Skip to content

Commit

Permalink
V2 changes and remove V1 completely (openviess#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
crazyfx1 authored Jul 14, 2021
1 parent 69fcbbd commit 6f0d1a0
Show file tree
Hide file tree
Showing 23 changed files with 5,244 additions and 72,195 deletions.
9 changes: 4 additions & 5 deletions PyViCare/PyViCareCachedService.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@

class ViCareCachedService(ViCareService):

def __init__(self, username, password, cacheDuration, token_file=None,circuit=0):
ViCareService.__init__(self, username, password, token_file, circuit)
def __init__(self, username, password, cacheDuration, client_id, token_file=None,circuit=0):
ViCareService.__init__(self, username, password, client_id, token_file, circuit)
self.cacheDuration = cacheDuration
self.cache = None
self.cacheTime = None
self.lock = threading.Lock()


def getProperty(self,property_name):
data = self.getOrUpdateCache()
entities = data["entities"]
entities = data["data"]
return readFeature(entities, property_name)

def setProperty(self,property_name,action,data):
Expand All @@ -26,7 +25,7 @@ def getOrUpdateCache(self):
self.lock.acquire()
try:
if self.isCacheInvalid():
url = apiURLBase + '/operational-data/installations/' + str(self.id) + '/gateways/' + str(self.serial) + '/devices/0/features/'
url = apiURLBase + '/equipment/installations/' + str(self.id) + '/gateways/' + str(self.serial) + '/devices/0/features/'
self.cache = self.get(url)
self.cacheTime = datetime.now()
return self.cache
Expand Down
45 changes: 0 additions & 45 deletions PyViCare/PyViCareCachedServiceV2.py

This file was deleted.

18 changes: 5 additions & 13 deletions PyViCare/PyViCareDevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import logging
from datetime import datetime, time
from PyViCare.PyViCareService import ViCareService
from PyViCare.PyViCareServiceV2 import ViCareServiceV2
from PyViCare.PyViCareCachedService import ViCareCachedService
from PyViCare.PyViCareCachedServiceV2 import ViCareCachedServiceV2
from PyViCare.PyViCare import handleNotSupported

import traceback
Expand Down Expand Up @@ -41,7 +39,7 @@ class Device:
"""

# TODO cirtcuit management should move at method level
def __init__(self, username, password,token_file=None,circuit=0,cacheDuration=0,customService=None,useV2=False,client_id=None):
def __init__(self, username, password,token_file=None,circuit=0,cacheDuration=0,customService=None,client_id=None):
"""Init function. Create the necessary oAuth2 sessions
Parameters
----------
Expand All @@ -57,15 +55,9 @@ def __init__(self, username, password,token_file=None,circuit=0,cacheDuration=0,
if customService is not None:
self.service = customService
elif cacheDuration == 0:
if useV2:
self.service = ViCareServiceV2(username, password, client_id, token_file, circuit)
else:
self.service = ViCareService(username, password, token_file, circuit)
self.service = ViCareService(username, password, client_id, token_file, circuit)
else:
if useV2:
self.service = ViCareCachedServiceV2(username, password, cacheDuration, client_id, token_file, circuit)
else:
self.service = ViCareCachedService(username, password, cacheDuration, token_file, circuit)
self.service = ViCareCachedService(username, password, cacheDuration, client_id, token_file, circuit)


""" Set the active mode
Expand Down Expand Up @@ -170,7 +162,7 @@ def getRoomTemperature(self):

@handleNotSupported
def getModes(self):
return self.service.getProperty("heating.circuits." + str(self.service.circuit) + ".operating.modes.active")["actions"][0]["fields"][0]["enum"]
return self.service.getProperty("heating.circuits." + str(self.service.circuit) + ".operating.modes.active")["commands"]["setMode"]["params"]["mode"]["constraints"]["enum"]

@handleNotSupported
def getActiveMode(self):
Expand All @@ -190,7 +182,7 @@ def getActiveProgram(self):

@handleNotSupported
def getPrograms(self):
return self.service.getProperty("heating.circuits." + str(self.service.circuit) + ".operating.programs")["entities"][9]["properties"]["components"]
return self.service.getProperty("heating.circuits." + str(self.service.circuit) + ".operating.programs")["components"]

@handleNotSupported
def getDesiredTemperatureForProgram(self , program):
Expand Down
18 changes: 1 addition & 17 deletions PyViCare/PyViCareHeatPump.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,4 @@ def getSupplyTemperaturePrimaryCircuit(self):

@handleNotSupported
def getReturnTemperaturePrimaryCircuit(self):
return self.service.getProperty("heating.primaryCircuit.sensors.temperature.return")["properties"]["value"]["value"]

@handleNotSupported
def getHeatingRodStatusOverall(self):
return self.service.getProperty("heating.heatingRod.status")["properties"]["overall"]["value"]

@handleNotSupported
def getHeatingRodStatusLevel1(self):
return self.service.getProperty("heating.heatingRod.status")["properties"]["level1"]["value"]

@handleNotSupported
def getHeatingRodStatusLevel2(self):
return self.service.getProperty("heating.heatingRod.status")["properties"]["level2"]["value"]

@handleNotSupported
def getHeatingRodStatusLevel3(self):
return self.service.getProperty("heating.heatingRod.status")["properties"]["level3"]["value"]
return self.service.getProperty("heating.primaryCircuit.sensors.temperature.return")["properties"]["value"]["value"]
71 changes: 46 additions & 25 deletions PyViCare/PyViCareService.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import TokenExpiredError
from oauthlib.oauth2 import TokenExpiredError

import requests
import re
import pickle
import os
import logging
import datetime
import pkce
from pickle import UnpicklingError
# This is required because "requests" uses simplejson if installed on the system

Expand All @@ -14,30 +16,28 @@
from PyViCare.PyViCare import PyViCareNotSupportedFeatureError, PyViCareRateLimitError
import PyViCare.Feature

client_id = '79742319e39245de5f91d15ff4cac2a8'
client_secret = '8ad97aceb92c5892e102b093c7c083fa'
authorizeURL = 'https://iam.viessmann.com/idp/v1/authorize'
token_url = 'https://iam.viessmann.com/idp/v1/token'
apiURLBase = 'https://api.viessmann-platform.io'
authorizeURL = 'https://iam.viessmann.com/idp/v2/authorize'
token_url = 'https://iam.viessmann.com/idp/v2/token'
apiURLBase = 'https://api.viessmann.com/iot/v1'
redirect_uri = "vicare://oauth-callback/everest"
viessmann_scope=["openid"]
viessmann_scope=["IoT User"]
logger = logging.getLogger('ViCare')
logger.addHandler(logging.NullHandler())


def readFeature(entities, property_name):
feature = next((f for f in entities if f["class"][0] == property_name and f["class"][1] == "feature"), None)
feature = next((f for f in entities if f["feature"] == property_name), None)

if(feature is None):
raise PyViCareNotSupportedFeatureError(property_name)

return feature

def buildSetPropertyUrl(id, serial, circuit, property_name, action):
return apiURLBase +'/operational-data/v1/installations/'+str(id)+'/gateways/'+str(serial)+'/devices/'+str(circuit)+'/features/'+property_name+'/'+action
return apiURLBase +'/equipment/installations/'+str(id)+'/gateways/'+str(serial)+'/devices/'+str(circuit)+'/features/'+property_name+'/'+action

def buildGetPropertyUrl(id, serial, circuit, property_name):
return apiURLBase + '/operational-data/installations/'+str(id)+'/gateways/'+str(serial)+'/devices/'+str(circuit)+'/features/'+property_name
return apiURLBase + '/equipment/installations/'+str(id)+'/gateways/'+str(serial)+'/devices/'+str(circuit)+'/features/'+property_name

# https://api.viessmann-platform.io/general-management/v1/installations/DDDDD gives the type like VitoconnectOptolink
# entities / "deviceType": "heating"
Expand All @@ -55,7 +55,7 @@ class ViCareService:
"""


def __init__(self, username, password,token_file=None,circuit=0):
def __init__(self, username, password, client_id, token_file=None,circuit=0):
"""Init function. Create the necessary oAuth2 sessions
Parameters
----------
Expand All @@ -72,6 +72,7 @@ def __init__(self, username, password,token_file=None,circuit=0):
self.password= password
self.token_file=token_file
self.circuit=circuit
self.client_id = client_id
self.oauth=self.__restoreToken(token_file)
self._getInstallations()
logger.info("Initialisation successful !")
Expand All @@ -97,7 +98,7 @@ def __restoreToken(self,token_file=None):
if (token_file!=None) and os.path.isfile(token_file):
try:
logger.info("Token file exists")
oauth = OAuth2Session(client_id,token=self._deserializeToken(token_file))
oauth = OAuth2Session(self.client_id,token=self._deserializeToken(token_file))
logger.info("Token restored from file")
except UnpicklingError:
logger.warning("Could not restore token")
Expand All @@ -124,9 +125,12 @@ def __getNewToken(self, username, password,token_file=None):
oauth:
oauth sessions object
"""
oauth = OAuth2Session(client_id, redirect_uri=redirect_uri,scope=viessmann_scope)
oauth = OAuth2Session(self.client_id, redirect_uri=redirect_uri,scope=viessmann_scope)
authorization_url, _ = oauth.authorization_url(authorizeURL)


# workaround until requests-oauthlib supports PKCE flow
code_verifier, code_challenge = pkce.generate_pkce_pair()
authorization_url += '&code_challenge=' + code_challenge + '&code_challenge_method=S256'
logger.debug("Auth URL is: "+authorization_url)

try:
Expand All @@ -141,7 +145,24 @@ def __getNewToken(self, username, password,token_file=None):
match = re.search("code=(.*)&",codestring)
codestring=match.group(1)
logger.debug("Codestring : "+codestring)
oauth.fetch_token(token_url, client_secret=client_secret,authorization_response=authorization_url,code=codestring)

# workaround until requests-oauthlib supports PKCE flow
resp = requests.post(url=token_url,
data={
'grant_type': 'authorization_code',
'client_id': self.client_id,
'redirect_uri': redirect_uri,
'code': codestring,
'code_verifier': code_verifier
}
)
result = resp.json()
token_dict = {
'access_token': result['access_token'],
'token_type': 'bearer'
}
oauth = OAuth2Session(client_id=self.client_id, token=token_dict)
# oauth.fetch_token(token_url, authorization_response=authorization_url,code=codestring)
logger.debug("Token received: ")
logger.debug(oauth)
logger.debug("Start serial")
Expand Down Expand Up @@ -179,17 +200,17 @@ def __get(self,url):
logger.debug(self.oauth)
r=self.oauth.get(url).json()
logger.debug("Response to get request: "+str(r))
self.handleExpiredToken(r)
self.handleRateLimit(r)

if(r=={'error': 'EXPIRED TOKEN'}):
logger.warning("Abnormal token, renewing") # apparently forged tokens TODO investigate
self.renewToken()
r = self.oauth.get(url).json()
return r
except TokenExpiredError:
self.renewToken()
return self.__get(url)

def handleExpiredToken(self, response):
if("error" in response and response["error"] == "EXPIRED TOKEN"):
raise TokenExpiredError(response)

def handleRateLimit(self, response):
if not PyViCare.Feature.raise_exception_on_rate_limit:
return
Expand Down Expand Up @@ -245,11 +266,11 @@ def _deserializeToken(self,token_file):
binary_file.close()

def _getInstallations(self):
self.installations = self.__get(apiURLBase+"/general-management/installations?expanded=true&")
#logger.debug("Installations: "+str(self.installations))
#self.href=self.installations["entities"][0]["links"][0]["href"]
self.id=self.installations["entities"][0]["properties"]["id"]
self.serial=self.installations["entities"][0]["entities"][0]["properties"]["serial"]
self.installations = self.__get(apiURLBase+"/equipment/installations?includeGateways=true")
installation = self.installations["data"][0]
self.id = installation["id"]
self.serial = installation["gateways"][0]["serial"]

return self.installations

def getInstallations(self):
Expand Down
Loading

0 comments on commit 6f0d1a0

Please sign in to comment.