YoLink Integration

I'll take a serious look at yolink AFTER that capability is released. But before then I would be 100% not interested.

  • Proprietary system with no published API? No thanks.
  • Proprietary system with only cloud integration? No thanks.

Sorry. Just not interested in more cloud2clound integrations when there are so many other options out there that ARE local - today. For instance, I would buy multiple of the yolink outdoor outlet immediately if it had local integration into HA, mqtt, or hubitat. But it doesn't, so I won't.

I just don't see why local MQTT couldn't done very quickly, and cheaply, on the development side of things if yolink WANTED TO. I've implemented MQTT on my custom LoRa implementations 4 different ways, it is not rocket science.

YoLink makes cool products, and I wish you guys luck. But that said, YoLink is just not a good fit for those that want local non-internet based home control... Just another "walled garden" ecosystem.

3 Likes

@Eric-YoLink Welcome to the forum! As the thread starter, I would like to say your system rocks!! It has saved our inventory in our commercial freezer. We are all looking to the day when your sensors report to our Hubitat dashboards. Personally speaking, if this requires a new access point, I am there. Congratulations on an amazing system. Looking forward to March 2022!

2 Likes

Instead of typing all this out, I am going to just +1 this. Tired of cloud stuff that breaks or goes out of business. Been burned too many times for that.

4 Likes

Agreed. And even more so when there’s proprietary protocols involved.

3 Likes

That's exactly why I chose to implement my own LoRa sensors.... I can support those forever, change gateways if needed, integrate it with whatever I want, etc. Not locked into anyone's proprietary system.

I get that not everyone can/wants to go through the very consumer unfriendly path of implementing LoRa or LoRaWAN themselves... But it worked for me.

Zwave LR is a better answer long term for most end consumers, as then you get pretty much all of the LoRa/YoLink benefit and none of the proprietary lock-in. It just needs to hurry up already.

I surmise that if YoLink hasn't made some local integration path before Zwave LR devices are available, it will greatly accelerate their demise/irrelevancy. But time will tell.

3 Likes

Managed to use the YoLink server API to query my temperature sensor with a bit of Python. Now just gotta figure out how to translate this to Groovy and wrap it in a HE driver.

import requests
import time
import json

yolinkURL='https://api.yosmart.com/open/yolink/v2/api' 
tokenURL = "https://api.yosmart.com/open/yolink/token"

# Use the YoLink app to generate these authentication credentials
UaID = 'ua_*******************************'
SecID = 'sec_***************************'

def get_access_token(url, client_id, client_secret):
	response = requests.post(
		url,
		data={"grant_type": "client_credentials"},
		auth=(client_id, client_secret),
	)
	return response.json()['access_token']

def post_command(url, token, method, methodData ):
	commandHeaders = {}
	commandHeaders['Content-type'] = 'application/json'
	commandHeaders['Authorization'] = 'Bearer ' + token

	commandData = methodData
	commandData['method'] = method
	commandData['time'] = str(int(time.time()*1000))

	response = requests.post(
		url,
		data=json.dumps(commandData),
		headers=commandHeaders
	)
	return response.json()

#get transaction token
token = get_access_token(tokenURL, UaID, SecID)

#get device list from server
responseData = post_command(yolinkURL, token, 'Home.getDeviceList', {} );
responseData = responseData['data']
deviceList	 = responseData['devices']

#find the temperature sensor device in the list
for device in deviceList :
	if device['type'] == 'THSensor':
		break
	else:
		sys.exit( "No Temperature Sensor Found")	

#capture the temperature sensor device identifiers			
thsensorToken	 = device['token']
thsensorDeviceID = device['deviceId']

#query the temperature sensor for all its parameters
responseData = post_command(yolinkURL, token, 'THSensor.getState', {
	'token'		   : thsensorToken,
	'targetDevice' : thsensorDeviceID,
	'params'	   : {} }
);

#drill down for the temperature
responseData = responseData['data']
thsensorState = responseData['state']
thsensorTemp = thsensorState[ 'temperature']

if thsensorState[ 'mode'] == 'f':
	thsensorTemp = round((thsensorTemp * 9/5) + 32,2)
else:
	thsensorTemp = round(thsensorTemp,2)

print(thsensorTemp, thsensorState[ 'mode'] )
print('\n')
4 Likes

@steve.hoge, just want to thank you for trying to implement this directly into HE and encourage you in your efforts. This is something that will benefit many (not in the least me!). I have been a fan of LoRa and Yolink sensors (except for the current Cloud dependency). Code on!

Looks like Yolink has just come out with a relatively inexpensive new Hub with TTS (Yolink SpeakerHub). It is compatible with their existing system and can also be used in conjunction with an existing Yolink Hub. If the API can be accessed and wrapped into a HE driver, this might be a good inexpensive local TTS device for HE as opposed to Sonos, or cloud based integrations such as Alexa etc. Sound quality as reviewed by one person is not so good apparently (the Yolink TTS hub is about the same size as the HE hub).

Personally, at this time, not wanting to go through the hoops of setting up the Echo Speaks App with certificate renewals etc given the risk that Alexa can cut this off at any time of their choosing, and the expense of compatible speakers in each room. So my current HE announcements go through the Alexa cloud via Alexa routines. However, with a limit of 200 routines, I am constantly having to weigh which routines I need to cull. Moving TTS announcements to the HE would free me from this limitation as well as freeing up Routine space on Alexa as well. Having TTS local on HE would be great but I don’t want to go through the expense of having a Sonos in each room in addition to my Echos.

If the Yolink API is made available (Yolink has hinted that it is working on local integration) then these new TTS hubs MAY be acceptable alternatives to more expensive speakers (if just used for announcements).

BTW, related, any news on Z-Wave LR?

@steve.hoge thanks I'm new to Hubitat and Groovy (like yesterday new :smile: ) but gave this a try based on your Python code (I love Python). This Driver so far will get the access token after you enter in your UAID and Secret Key, save, then press configure. This will also then populate the devicesList state variable after you refresh. Then you can plugin the deviceToken and deviceID in the preferences and save, and now use the refresh button to pull those state variables from the sensor. Note I'm using this with a motion sensor, so be sure to change the part to your device type if not using MotionSenor in the getMotionSensorDetails command and also the updateState logic to reflect your state variables.

I don't quite know how to setup polling or use the HTTP Callback API for YoLink I see mentioned on the API docs so that the state would update based on a motion event. Sorry if it is a little rough, still learning everything but wanted to share in case it helps anyone else get closer to something working.

/*
 * YoLink API
 *
 * Talks to the YoLink API using user credentials from mobile app
 * Based off Python code by @steve.hoge and using snippets of various project examples including: https://github.com/hubitat/HubitatPublic/tree/master/examples/drivers
 * 
 */
metadata {
    definition(name: "YoLink API", namespace: "community", author: "Dev Dre4ms") {
        capability "Configuration"
        command "refresh"
        
        attribute "access_token", "text"
        attribute "deviceList", "text"
       
        attribute "alertInterval", "text"
        attribute "battery", "text"
        attribute "devTemperature", "text"
        attribute "ledAlarm", "text"
        attribute "nomotionDelay", "text"
        attribute "sensitivity", "text"
        attribute "state", "text"
        attribute "lastMotion", "text"
        
    }
}

preferences
{
    section
    {
        input "uaid", "text", title: "YoLink UAID", required: true
        input "secret", "password", title: "YoLink Secret Key", required: true
        input "deviceID", "text", title: "Device ID", required: false
        input "deviceToken", "text", title: "Device Token", required: false
        input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: false
    }
}


def refresh() {
    def motionState = getMotionSensorDetails(state.access_token, deviceToken, deviceID)
    updateState(motionState)
    logDebug("Motion state status:${motionState}")
}

def updateState(Map motionState)
{
    state.alertInterval = motionState.data.state.alertInterval
    state.battery = motionState.data.state.battery
    state.devTemperature = motionState.data.state.devTemperature
    state.ledAlarm = motionState.data.state.ledAlarm
    state.nomotionDelay = motionState.data.state.nomotionDelay
    state.sensitivity = motionState.data.state.sensitivity
    state.state = motionState.data.state.state
    state.lastMotion = motionState.data.reportAt
    
    
}

def logDebug(msg) 
{
    if (logEnable)
    {
        log.debug(msg)
    }
}


def configure()
{
    getAccessToken()

    //Get devicelist and update attribute to see deviceToken/deviceId
    def command_response = post_command(state.access_token, "Home.getDeviceList", [:])
    logDebug("Devices List: ${command_response}")
    state.devicesList = command_response
    
}

def getAccessToken()
{
    def postParams = genParamsPre()
    postParams['body'] = ["grant_type": "client_credentials"]
    try
    {
        token_response = httpPostExec(postParams, false)      
        logDebug("Received access token: ${token_response.access_token}")
        state.access_token = token_response.access_token
        
        return token_response.access_token
    }
    catch (Exception e)
    {
        logDebug("Error retrieving access token from YoLinkAPI")
    }
    
}

def getTokenURI()
{
    return "https://api.yosmart.com/open/yolink/token"
}

def getAPIURI()
{
    return "https://api.yosmart.com/open/yolink/v2/api"
}

def genParamsPre()
{
    def params =
        [
            uri: getTokenURI(),
            headers:
            [
                'Authorization': "Basic " + ("${uaid}:${secret}").bytes.encodeBase64().toString()
            ],
            contentType: 'application/json'
        ]
 
    return params
}

def genCommandParams(String token)
{
    def params =
        [
            uri: getAPIURI(),
            headers:
            [
                'Authorization': "Bearer " + token
            ],
            contentType: 'application/json'
        ]
 
    return params
}

def httpPostExec(params, throwToCaller = false)
{
    logDebug("httpPostExec(${params})")
    def response = [:]
    try
    {
        httpPost(params) { resp ->
            if (resp.success)
            {
                
                response = resp.getData()
                logDebug("Success: ${response}")
            }
            
        }
        
    }
    catch (Exception e)
    {
        logDebug("httpPostExec() failed: ${e.message}")
        if(throwToCaller)
        {
            throw(e)
        }
    }
    return response
}

def getCurrentTime() {
    now = new Date()
    return now.getTime()
}

def post_command(String token, String method, Map methodData) {
	def params = genCommandParams(token)

	def commandData = methodData
	commandData['method'] = method
    commandData['time'] = getCurrentTime().toString()
    params["body"] = commandData
    
	return httpPostExec(params, true)
}

def getMotionSensorDetails(String token, String deviceToken, String deviceID)
{
    //This needs to be updated for the type of sensor you'd like to get the state from, replace MotionSensor.getState with your device type
    def deviceData = post_command(token, 'MotionSensor.getState', [
    'token': deviceToken,
    'targetDevice': deviceID,
    'params': [:]])
}

def logsOff() {
    log.warn "debug logging disabled..."
    device.updateSetting("logEnable", [value: "false", type: "bool"])
}
1 Like

Very cool! :smiley: I got it basically installed and talking to my Hub and it's listing my devices!

But I don't have a motion sensor and don't know enough about Groovy drivers yet to exactly know how to modify this based on querying my THSensor and my DoorSensor, so it's throwing the expected errors until I can modify it to talk to them.

Thanks for including the link to HubitatPublic/examples/drivers at master · hubitat/HubitatPublic · GitHub , I will be perusing that to try and understand Groovy driver semantics and control flow.

Google App Script to read temperature Sensor and better understand how the API works.

Enjoy !,  James


   // Google Apps Script
  // coded by James Wiser version 2-21-22 contact james@wisermail.com

function myFunction() {
  
  // console.logs are included to help you gain clarity in what is going on
  
  // API Reference Guide http://doc.yosmart.com/docs/overall/intro
  // yolinkURL='https://api.yosmart.com/open/yolink/v2/api' 
  // tokenURL = "https://api.yosmart.com/open/yolink/token"
 
  // client_id and client_secret are obtained from the app on your phone.  keep these private
  var client_id = 'ua_********************************';
  var client_secret = 'sec_***************************';
  
  var access_token_url = 'https://api.yosmart.com/open/yolink/token';
  var yolinkurl = "https://api.yosmart.com/open/yolink/v2/api";

  
  // authentication to get token from Yolink
  var data = {
    'grant_type' : 'client_credentials', 
   };

  var options = {
    'method': 'post',
    'payload': data,
    'headers': {
      'Authorization': 'Basic ' + Utilities.base64Encode(`${client_id}:${client_secret}`),
    },
  };
  var resp = UrlFetchApp.fetch(access_token_url, options);
  
  console.log(JSON.parse(resp.getContentText()));
  var data = JSON.parse(resp.getContentText());
  var token = data['access_token'];
  console.log(token);

// call API to get a valid list of devices
var now = new Date();
var timems = now.getTime();
console.log(timems);

// payload elements can be referenced in API Reference Guide
var payload = {
            
            "time": timems,
            "method": "Home.getDeviceList",
            
        }; 


// use the token that Yolink gave you
var options = {
    "method": "post",
     "Content-Type": "application/json",
     "headers": {'Authorization': 'Bearer '+  token},
     "payload" : payload  
    }


  var resp2 = UrlFetchApp.fetch(yolinkurl, options);
  
  console.log(JSON.parse(resp2.getContentText()));
  
  var respdata = JSON.parse(resp2.getContentText());
  var yolinkdata = respdata['data'];
  var devicelist = respdata['data']['devices'];
  // 0 id the 1st device in the data set, 1 would be the next and so on...
  var device = respdata['data']['devices'][0].deviceId;
    
  console.log(yolinkdata);
  console.log(devicelist);
  console.log(device);

  // find the first device that is a tempreature sensor
  var numofdevices = devicelist.length;
  for (var counter = 0; counter < numofdevices; counter = counter + 1) {
    console.log(respdata['data']['devices'][counter].deviceId);
    // type comes from API Reference Guide
    if (respdata['data']['devices'][counter].type == 'THSensor') {
        var ThSensor_token  = respdata['data']['devices'][counter].token;
        var ThSensor_deviceId  = respdata['data']['devices'][counter].deviceId;
        var TheSensor_name = respdata['data']['devices'][counter].name;
        break;
    }   
  }
  
 // format the next request with the required elements for the device. refer to API Reference Guide
 var payload = {
            
            "time": timems,
            "method": "THSensor.getState",
            "targetDevice": ThSensor_deviceId,
            "token" : ThSensor_token,
            
        }; 


var options = {
    "method": "post",
     "Content-Type": "application/json",
     "headers": {'Authorization': 'Bearer '+  token},
     "payload" : payload  
    }


  var resp3 = UrlFetchApp.fetch(yolinkurl, options);
  console.log(JSON.parse(resp3.getContentText()));
  
  var respdata = JSON.parse(resp3.getContentText());

  var tempsens = respdata['data']['state'].temperature
  var tempmode = respdata['data']['state'].mode
  var TheSensor_ts = respdata['data']['reportAt'];
  if (tempmode == 'f' ){
     temp = (tempsens * 9/5) + 32;
     temp = temp.toFixed(1);
  }
 else{
      temp = temp.toFixed(1);
      tempmode = 'c';
     }
  
  // use the date/time stamp that comes from the data set to accurately report the last reading
  var date = new Date(TheSensor_ts);
  // use the timezone you are working in
  var fdate = Utilities.formatDate(date, 'America/Chicago','MM-dd-yy hh:mm:ss aa');
  
  console.log(TheSensor_name + " is " +  temp + " " + tempmode + " " + fdate);

   
  }


2 Likes

They claim they will come out with an API in 2022.

If they come out with a LOCAL API, I'd be all over their devices. If cloud... Not so much.

Hi @steve.hoge,
Any further progress on the cloud API integration with Hubitat? I realize that the current API exposed by Yolink is cloud based, but for now, I am OK with this at this point unlike some here in the community. Even if cloud based, an integration app for noob users like me (non-coders) which could be uploaded like the Ecobee Hubitat cloud integration would be fantastic! Thanks for your contributions to this effort!

Have taken a break from Hubitat projects for awhile but will get back to it eventually. I was kind of hoping based on the proof-of-concept prototypes here that @Hubitat_Staff might pick up the YoLink ball and run with it :wink:

Yes, was hoping that Hubitat staff would pick up on this as well but I think that they are philosophically set on local only integrations (which I understand) and so are relying on community based integrations for anything that has to access API that are cloud based. Anyway, thanks for all your efforts and let us all know if (and hopefully not just if, but when) you decide to develop this further. Thanks again.

1 Like

Anyone have a good Z-wave water thermostat solution that integrates with Hubitat. Need it to monitor lake temperature in the summer. Probably installing it under my dock.

2 Likes

Any news on yolink and their cloud API? Is anyone working on a cloud integration driver for HE?

I don't think so...

1 Like