Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 61 additions & 34 deletions sc2mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

from colorlog import ColoredFormatter

import getopt
import sys


def setup_logger(name):
"""Return a logger with a default ColoredFormatter."""
Expand All @@ -39,10 +42,25 @@ def setup_logger(name):
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

if loglevel:
logger.setLevel(getattr(logging, loglevel.upper(), None))
else:
logger.setLevel(logging.DEBUG)

if logfile:
fileformatter = ColoredFormatter(
"[%(levelname)-8s]-%(asctime)s %(message)s",
datefmt='%Y-%m-%d %H:%M:%S',
reset=False
)
filehandler = logging.FileHandler(logfile)
filehandler.setFormatter(fileformatter)
logger.addHandler(filehandler)

return logger


STATLIMITS = [
{ "mask": r"LOCK_STATE.*DOOR", "check": "door_locked", "fail": 1 },
{ "mask": r"OPEN_STATE.*DOOR", "check": "door_closed", "fail": 0 },
Expand All @@ -52,12 +70,27 @@ def setup_logger(name):
{ "mask": r"STATE.*COVER", "check": "window_closed", "fail": 0 },
]

# Defaults and Command line
logfile=""
configfile="config.json"
loglevel="DEBUG"
opts, args = getopt.getopt(sys.argv[1:], 'f:l:c:', ['logfile=', 'loglevel=', 'configfile='])
for opt, arg in opts:
if opt in ('-f', '--logfile'):
logfile=arg
elif opt in ('-l', '--loglevel'):
loglevel=arg
elif opt in ('-c', '--configfile'):
configfile=arg

#logging.basicConfig(level=logging.INFO)
_LOGGER = setup_logger("s2m")


async def main():

try:
with open("config.json", "r") as cfile:
with open(configfile, "r") as cfile:
cfo = json.load(cfile)

for el in ["user", "password", "broker"]:
Expand All @@ -67,7 +100,16 @@ async def main():
ad = SkodaAdapter(cfo["user"], cfo["password"])
await ad.init()
mqttc = mqtt.Client()
mqttc.connect(cfo["broker"])
if "brokeruser" in cfo and "brokerpassword" in cfo:
mqttc.username_pw_set(username=cfo["brokeruser"],password=cfo["brokerpassword"])
if "brokerport" not in cfo:
cfo["brokerport"] == 1883
try:
mqttc.connect(cfo["broker"],port=int(cfo["brokerport"]))
except:
_LOGGER.critical("Connection to broker failed")
exit(1)

mqttc.loop_start()

loop = asyncio.get_event_loop()
Expand Down Expand Up @@ -102,7 +144,6 @@ async def configSample():
}, cfile)



class VWThrottledException(Exception):
# attributes:
# message
Expand Down Expand Up @@ -133,7 +174,6 @@ class SkodaAdapter:
'id_token': 'jwtid_token'
}


statusValues = {
"0x0203010001":{"statusName": "MAINTENANCE_INTERVAL_DISTANCE_TO_OIL_CHANGE", "unit_of_measurement": "km"},
"0x0203010002":{"statusName": "MAINTENANCE_INTERVAL_TIME_TO_OIL_CHANGE", "unit_of_measurement": "days"},
Expand Down Expand Up @@ -303,8 +343,6 @@ class SkodaAdapter:
# extract_csrf = lambda self,req: re.compile('<meta name="_csrf" content="([^"]*)"/>').search(req).group(1)




async def login(self):
## Following does not work yet really, needs adjustments for Skoda
#_LOGGER.info("Getting landing page (%s)" % self.landing_page_url)
Expand Down Expand Up @@ -401,6 +439,7 @@ async def updateValues(self, mqttc):

await asyncio.sleep(60)


async def getVehicleStatus(self, vin):
url = await self.replaceVarInUrl("$homeregion/fs-car/bs/vsr/v1/$type/$country/vehicles/$vin/status", vin)
accept = "application/json"
Expand All @@ -420,7 +459,6 @@ async def getVehicleStatus(self, vin):
self.vehicleStates[vin] = dict([(e["id"],e if "value" in e else "") for f in [s["field"] for s in r["StoredVehicleDataResponse"]["vehicleData"]["data"]] for e in f])



async def getVehicleStatus_orig(self, vin, url, path, element, element2, element3, element4):
url = await self.replaceVarInUrl(url, vin)
accept = "application/json"
Expand Down Expand Up @@ -461,7 +499,6 @@ async def getVehicleStatus_orig(self, vin, url, path, element, element2, element
if element4 and element4 in result:
result = result[element4]


if path == "tripdata":
if self.config["tripType"] == "none":
return
Expand All @@ -471,9 +508,6 @@ async def getVehicleStatus_orig(self, vin, url, path, element, element2, element
for v in result:
pass




return r


Expand All @@ -484,7 +518,8 @@ async def replaceVarInUrl(self, url, vin = ""):
if vin != "":
nurl = re.sub("\$vin", vin, nurl)
return nurl



async def getVehicleData(self,vin):
url = await self.replaceVarInUrl("https://msg.volkswagen.de/fs-car/promoter/portfolio/v1/$type/$country/vehicle/$vin/carportdata", vin)
r = await self.execRequest({
Expand All @@ -503,6 +538,7 @@ async def getVehicleData(self,vin):
self.vehicleData[vin] = r.json()
return r


async def getVehicleRights(self,vin):
url = "https://mal-1a.prd.ece.vwg-connect.com/api/rolesrights/operationlist/v3/vehicles/" + vin
r = await self.execRequest({
Expand All @@ -520,12 +556,15 @@ async def getVehicleRights(self,vin):
self.vehicleRights[vin] = r.json()
return r


async def saveTokens(self):
pass # TODO


async def loadTokens(self):
pass # TODO


async def requestStatusUpdate(self, vin = ""):

key = "skoda2mqtt.requestStatusUpdateTS"
Expand Down Expand Up @@ -590,10 +629,7 @@ async def getHomeRegion(self, vin = ""):
self.config["homeregion"] = r.json()['homeRegion']['baseUri']['content'].split("/api")[0].replace("mal-", "fal-") if r.json()['homeRegion']['baseUri']['content'] != "https://mal-1a.prd.ece.vwg-connect.com/api" else "https://msg.volkswagen.de"
return r






async def getVehicles(self):
url = await self.replaceVarInUrl("https://msg.volkswagen.de/fs-car/usermanagement/users/v1/$type/$country/vehicles")
headers = {
Expand All @@ -611,6 +647,7 @@ async def getVehicles(self):
self.vehicles = r.json()['userVehicles']['vehicle']
return r.json()


async def getCodeChallenge(self):
chash = ""
result = ""
Expand All @@ -623,6 +660,7 @@ async def getCodeChallenge(self):
chash = b64encode(sha256.digest()).decode("utf-8")[:-1]
return(result, chash)


async def getTokens(self, rurl, code_verifier = ""):
hashArray = re.split(r'[?#]',rurl)[-1].split('&')
tokens = {}
Expand Down Expand Up @@ -658,14 +696,14 @@ async def getTokens(self, rurl, code_verifier = ""):
await self.getVWTokens(vwtok, tokens['jwtid_token'])
return (r.json(), tokens)


async def loopRefreshTokens(self):
while True:
#await asyncio.sleep(3600 * 0.9)
await asyncio.sleep(20)
#await self.refreshToken()



async def refreshToken(self):
url = "https://tokenrefreshservice.apps.emea.vwapps.io/refreshTokens"
data = {
Expand All @@ -682,11 +720,7 @@ async def refreshToken(self):
if "refresh_token" in rtokens:
self.vwtokens["rtoken"] = rtokens["refresh_token"]







async def getVWTokens(self, tokens, jwtid_token):

self.vwtokens["atoken"] = tokens["access_token"]
Expand Down Expand Up @@ -719,7 +753,6 @@ async def getVWTokens(self, tokens, jwtid_token):
pass



async def getNonce(self):
ts = "%d" % (time.time())
sha256 = hashlib.sha256()
Expand Down Expand Up @@ -776,6 +809,7 @@ async def execRequest(self, req):
#print(r.status_code)
return r


async def getauth(self,getconfig):
_LOGGER.info("Getting authorization...")
getauth = await self.execRequest({ #
Expand Down Expand Up @@ -866,7 +900,6 @@ async def postpw(self, ppwurl, pwform, getconfig, postemail):
pass
_LOGGER.info("Done!")


if not excepted:
raise Exception("We should have received an exception by now, so wtf?")
#if postpw.status_code >= 400:
Expand All @@ -891,11 +924,7 @@ async def postpw(self, ppwurl, pwform, getconfig, postemail):
# userId = (await self.tokenize(postpw.headers["Location"]))["userId"] if "userId" in (await self.tokenize(postpw.headers["Location"])) and userId == "" else userId
_LOGGER.info("Done!")
await self.getTokens(skodaURL)







def __init__(self, email, password):
self.config = {
Expand All @@ -910,19 +939,17 @@ def __init__(self, email, password):
self.config["email"] = email
self.config["password"] = password


async def init(self):
if len(self.vehicles) == 0:
await self.login()
v = (await self.getVehicles())['userVehicles']['vehicle']
for car in self.vehicles:
s = await self.getVehicleData(car)
t = await self.getVehicleRights(car)
# t = await self.getVehicleRights(car)
hr = await self.getHomeRegion(car)
rq = await self.getVehicleStatus(car)





if __name__ == "__main__":
asyncio.run(main())