diff --git a/sc2mqtt.py b/sc2mqtt.py index ab6e0c9..c3ddd60 100755 --- a/sc2mqtt.py +++ b/sc2mqtt.py @@ -19,6 +19,9 @@ from colorlog import ColoredFormatter +import getopt +import sys + def setup_logger(name): """Return a logger with a default ColoredFormatter.""" @@ -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 }, @@ -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"]: @@ -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() @@ -102,7 +144,6 @@ async def configSample(): }, cfile) - class VWThrottledException(Exception): # attributes: # message @@ -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"}, @@ -303,8 +343,6 @@ class SkodaAdapter: # extract_csrf = lambda self,req: re.compile('').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) @@ -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" @@ -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" @@ -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 @@ -471,9 +508,6 @@ async def getVehicleStatus_orig(self, vin, url, path, element, element2, element for v in result: pass - - - return r @@ -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({ @@ -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({ @@ -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" @@ -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 = { @@ -611,6 +647,7 @@ async def getVehicles(self): self.vehicles = r.json()['userVehicles']['vehicle'] return r.json() + async def getCodeChallenge(self): chash = "" result = "" @@ -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 = {} @@ -658,6 +696,7 @@ 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) @@ -665,7 +704,6 @@ async def loopRefreshTokens(self): #await self.refreshToken() - async def refreshToken(self): url = "https://tokenrefreshservice.apps.emea.vwapps.io/refreshTokens" data = { @@ -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"] @@ -719,7 +753,6 @@ async def getVWTokens(self, tokens, jwtid_token): pass - async def getNonce(self): ts = "%d" % (time.time()) sha256 = hashlib.sha256() @@ -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({ # @@ -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: @@ -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 = { @@ -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())