Exemplo n.º 1
0
def main():
    parser = ArgumentParser()
    parser.add_argument('username',
                        help='Tesla username, usually an email address')
    parser.add_argument('--output', type=FileType('w'), default='-')
    parser.add_argument('--start', type=parse_date)
    parser.add_argument('--end', type=parse_date)
    args = parser.parse_args()

    password = os.environ.get('TESLA_PASSWORD')
    if not password:
        password = getpass(f'Password for {args.username}:')

    tesla = Tesla(args.username, password)

    writer = DictWriter(
        args.output,
        fieldnames=['battery_id', 'serial_number', 'timestamp', 'solar_power'])
    writer.writeheader()

    for battery in tesla.battery_list():
        battery_id = battery['id']
        for end_date in tesla_end_dates(args.start, args.end):
            data = battery.get_calendar_history_data(kind='power',
                                                     end_date=end_date)

            assert data["installation_time_zone"] == "Europe/London"

            serial_number = data['serial_number']
            for row in data['time_series']:
                writer.writerow(
                    dict(battery_id=battery_id,
                         serial_number=serial_number,
                         timestamp=row['timestamp'],
                         solar_power=row['solar_power']))
Exemplo n.º 2
0
def main():
    default_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO,
                        format=default_format)
    if not args.verify:
        # Disable SSL verify for Nominatim
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        geopy.geocoders.options.default_ssl_context = ctx
    email = raw_input('Enter email: ')
    with Tesla(email, verify=args.verify, proxy=args.proxy) as tesla:
        if (webdriver and args.web is not None) or webview:
            tesla.authenticator = custom_auth
        if args.timeout:
            tesla.timeout = args.timeout
        vehicles = tesla.vehicle_list()
        print('-' * 80)
        fmt = '{:2} {:25} {:25} {:25}'
        print(fmt.format('ID', 'Display name', 'VIN', 'State'))
        for i, vehicle in enumerate(vehicles):
            print(
                fmt.format(i, vehicle['display_name'], vehicle['vin'],
                           vehicle['state']))
        print('-' * 80)
        idx = int(raw_input("Select vehicle: "))
        print('-' * 80)
        print('VIN decode:', ', '.join(vehicles[idx].decode_vin().values()))
        print('Option codes:', ', '.join(vehicles[idx].option_code_list()))
        print('-' * 80)
        menu(vehicles[idx])
Exemplo n.º 3
0
def main():
    default_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    logging.basicConfig(level=logging.DEBUG, format=default_format)
    email = raw_input('Enter email: ')
    password = getpass.getpass('Password: '******'-' * 80)
        fmt = '{:2} {:25} {:25} {:25}'
        print(fmt.format('ID', 'Display name', 'VIN', 'State'))
        for i, vehicle in enumerate(vehicles):
            print(
                fmt.format(i, vehicle['display_name'], vehicle['vin'],
                           vehicle['state']))
        print('-' * 80)
        idx = int(raw_input("Select vehicle: "))
        print('-' * 80)
        print('VIN decode:', ', '.join(vehicles[idx].decode_vin().values()))
        print('Option codes:', ', '.join(vehicles[idx].option_code_list()))
        print('-' * 80)
        menu(vehicles[idx])
Exemplo n.º 4
0
def main():
    parser = argparse.ArgumentParser(description='Tesla Owner API CLI')
    parser.add_argument('-e', dest='email', help='login email', required=True)
    parser.add_argument('-p',
                        dest='password',
                        nargs='?',
                        const='',
                        help='prompt/specify login password')
    parser.add_argument('-t', dest='passcode', help='two factor passcode')
    parser.add_argument('-u', dest='factor', help='use two factor device name')
    parser.add_argument('-f', dest='filter', help='filter on id, vin, etc.')
    parser.add_argument('-a', dest='api', help='API call endpoint name')
    parser.add_argument('-k',
                        dest='keyvalue',
                        help='API parameter (key=value)',
                        action='append',
                        type=lambda kv: kv.split('=', 1))
    parser.add_argument('-c', dest='command', help='vehicle command endpoint')
    parser.add_argument('-l',
                        '--list',
                        action='store_true',
                        help='list all selected vehicles')
    parser.add_argument('-o',
                        '--option',
                        action='store_true',
                        help='list vehicle option codes')
    parser.add_argument('-v',
                        '--vin',
                        action='store_true',
                        help='vehicle identification number decode')
    parser.add_argument('-w',
                        '--wake',
                        action='store_true',
                        help='wake up selected vehicle(s)')
    parser.add_argument('-g',
                        '--get',
                        action='store_true',
                        help='get rollup of all vehicle data')
    parser.add_argument('-n',
                        '--nearby',
                        action='store_true',
                        help='list nearby charging sites')
    parser.add_argument('-m',
                        '--mobile',
                        action='store_true',
                        help='get mobile enabled state')
    parser.add_argument('-s',
                        '--start',
                        action='store_true',
                        help='remote start drive')
    parser.add_argument('-d',
                        '--debug',
                        action='store_true',
                        help='set logging level to debug')
    args = parser.parse_args()
    default_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO,
                        format=default_format)
    if args.password == '':
        password = getpass.getpass('Password: '******'%d vehicle(s), %d selected', len(cars), len(selected))
        for i, vehicle in enumerate(selected):
            print('Vehicle %d:' % i)
            if args.list:
                print(vehicle)
            if args.option:
                print(', '.join(vehicle.option_code_list()))
            if args.vin:
                print(vehicle.decode_vin())
            if args.wake:
                vehicle.sync_wake_up()
            if args.get:
                print(vehicle.get_vehicle_data())
            if args.nearby:
                print(vehicle.get_nearby_charging_sites())
            if args.mobile:
                print(vehicle.mobile_enabled())
            if args.start:
                print(vehicle.remote_start_drive())
            if args.api:
                data = dict(args.keyvalue) if args.keyvalue else {}
                print(vehicle.api(args.api, **data))
Exemplo n.º 5
0
def main():
    default_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO,
                        format=default_format)
    with Tesla(args.email,
               verify=args.verify,
               proxy=args.proxy,
               sso_base_url=args.url) as tesla:
        if (webdriver and args.web is not None) or webview:
            tesla.authenticator = custom_auth
        if args.timeout:
            tesla.timeout = args.timeout
        if args.refresh:
            tesla.refresh_token(refresh_token=args.refresh)
        selected = prod = tesla.vehicle_list() + tesla.battery_list()
        if args.filter:
            selected = [
                p for p in prod for v in p.values() if v == args.filter
            ]
        logging.info('%d product(s), %d selected', len(prod), len(selected))
        for i, product in enumerate(selected):
            print('Product %d:' % i)
            # Show information or invoke API depending on arguments
            if args.list:
                print(product)
            if isinstance(product, Vehicle):
                if args.option:
                    print(', '.join(product.option_code_list()))
                if args.vin:
                    print(product.decode_vin())
                if args.wake:
                    product.sync_wake_up()
                if args.get:
                    print(product.get_vehicle_data())
                if args.nearby:
                    print(product.get_nearby_charging_sites())
                if args.mobile:
                    print(product.mobile_enabled())
                if args.stream:
                    product.stream(print)
                if args.service:
                    print(product.get_service_scheduling_data())
                if args.history:
                    print(product.get_charge_history())
            elif isinstance(product, Battery) and args.battery:
                print(product.get_battery_data())
            elif isinstance(product, SolarPanel) and args.site:
                print(product.get_site_data())
            if args.api or args.command:
                data = {}
                for key, value in args.keyvalue or []:
                    try:
                        data[key] = ast.literal_eval(value)
                    except (SyntaxError, ValueError):
                        data[key] = value
                if args.api:
                    for commandargs in args.api:
                        command = commandargs.pop(0)
                        command_data = {}
                        for keyvalue in commandargs or []:
                            key, value = keyvalue.split('=', 1)
                            try:
                                command_data[key] = ast.literal_eval(value)
                            except (SyntaxError, ValueError):
                                command_data[key] = value
                        if not command_data:
                            command_data = data
                        print(product.api(command, **command_data))
                else:
                    print(product.command(args.command, **data))
            if args.user:
                print(product.get_user_details())
        if args.logout:
            if webview and not (webdriver and args.web is not None):
                window = webview.create_window('Logout', tesla.logout())
                webview.start()
            else:
                tesla.logout(not (webdriver and args.web is not None))
Exemplo n.º 6
0
def main():
    parser = argparse.ArgumentParser(description='Tesla Owner API CLI')
    parser.add_argument('-e', dest='email', help='login email', required=True)
    parser.add_argument('-p',
                        dest='password',
                        nargs='?',
                        const='',
                        help='prompt/specify login password')
    parser.add_argument('-t', dest='passcode', help='two factor passcode')
    parser.add_argument('-u', dest='factor', help='use two factor device name')
    parser.add_argument('-f', dest='filter', help='filter on id, vin, etc.')
    parser.add_argument('-a', dest='api', help='API call endpoint name')
    parser.add_argument('-k',
                        dest='keyvalue',
                        help='API parameter (key=value)',
                        action='append',
                        type=lambda kv: kv.split('=', 1))
    parser.add_argument('-c', dest='command', help='product command endpoint')
    parser.add_argument('-l',
                        '--list',
                        action='store_true',
                        help='list all selected vehicles/batteries')
    parser.add_argument('-o',
                        '--option',
                        action='store_true',
                        help='list vehicle option codes')
    parser.add_argument('-v',
                        '--vin',
                        action='store_true',
                        help='vehicle identification number decode')
    parser.add_argument('-w',
                        '--wake',
                        action='store_true',
                        help='wake up selected vehicle(s)')
    parser.add_argument('-g',
                        '--get',
                        action='store_true',
                        help='get rollup of all vehicle data')
    parser.add_argument('-b',
                        '--battery',
                        action='store_true',
                        help='get detailed battery state and config')
    parser.add_argument('-n',
                        '--nearby',
                        action='store_true',
                        help='list nearby charging sites')
    parser.add_argument('-m',
                        '--mobile',
                        action='store_true',
                        help='get mobile enabled state')
    parser.add_argument('-s',
                        '--start',
                        action='store_true',
                        help='remote start drive')
    parser.add_argument('-d',
                        '--debug',
                        action='store_true',
                        help='set logging level to debug')
    args = parser.parse_args()
    default_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO,
                        format=default_format)
    if args.password == '':
        password = getpass.getpass('Password: '******'%d product(s), %d selected', len(prod), len(selected))
        for i, product in enumerate(selected):
            print('Product %d:' % i)
            # Show information or invoke API depending on arguments
            if args.list:
                print(product)
            if isinstance(product, Vehicle):
                if args.option:
                    print(', '.join(product.option_code_list()))
                if args.vin:
                    print(product.decode_vin())
                if args.wake:
                    product.sync_wake_up()
                if args.get:
                    print(product.get_vehicle_data())
                if args.nearby:
                    print(product.get_nearby_charging_sites())
                if args.mobile:
                    print(product.mobile_enabled())
                if args.start:
                    print(product.remote_start_drive())
            elif args.battery:
                print(product.get_battery_data())
            if args.api:
                data = {}
                for key, value in args.keyvalue or []:
                    try:
                        data[key] = ast.literal_eval(value)
                    except ValueError:
                        data[key] = value
                print(product.api(args.api, **data))
Exemplo n.º 7
0
 def __tesla_connect(self, email):
     """
     Connect to vehicles associated with email address
     """
     self.tesla = Tesla(email)
     self.tesla.fetch_token()
Exemplo n.º 8
0
class TeslaAPIHandler():
    def __init__(self, bismuth, reg, unreg, op_data):
        self.address = "Bis1TeSLaWhTC2ByEwZnYWtsPVK5428uqnL46"
        self.bismuth = bismuth
        self.register = reg
        self.unregister = unreg
        self.op_data = op_data

    def fetch_vehicle_data(self, email, pwd):
        """
        Returns a dict with vehicle data for all VINs using Tesla API
        """
        try:
            data = self.tesla_data(email)
        except:
            print("Vehicle unavailable, trying again. ")
            data = self.tesla_data(email)

        N = data["count"]
        out = {}
        out["total"] = N
        out["vin"] = {}

        for i in range(0, N):
            vin = data["vehicle"][i]["vin"]
            if len(pwd) > 0:
                vin = (vin + pwd).encode("utf-8")
                vin = hashlib.sha256(vin).hexdigest()
            checksum = self.checksum(vin, True)
            vin = vin + checksum
            out["vin"][str(i)] = vin
            out[vin] = {}
            out[vin]["battery_type"] = data["vehicle"][i]["battery_type"]
            out[vin]["battery_level"] = data["vehicle"][i]["battery_level"]
            out[vin]["battery_range"] = data["vehicle"][i]["battery_range"]
            out[vin]["usable_battery_level"] = data["vehicle"][i][
                "usable_battery_level"]
            out[vin]["charge_current_request"] = data["vehicle"][i][
                "charge_current_request"]
            out[vin]["charge_energy_added"] = data["vehicle"][i][
                "charge_energy_added"]
            out[vin]["charge_miles_added_ideal"] = data["vehicle"][i][
                "charge_miles_added_ideal"]
            out[vin]["charge_miles_added_rated"] = data["vehicle"][i][
                "charge_miles_added_rated"]
            out[vin]["ideal_battery_range"] = data["vehicle"][i][
                "ideal_battery_range"]
            out[vin]["est_battery_range"] = data["vehicle"][i][
                "est_battery_range"]
            out[vin]["outside_temp"] = json.dumps(
                data["vehicle"][i]["outside_temp"])
            out[vin]["inside_temp"] = json.dumps(
                data["vehicle"][i]["inside_temp"])
            out[vin]["odometer"] = data["vehicle"][i]["odometer"]
            out[vin]["timestamp"] = data["vehicle"][i]["timestamp"]
            out[vin]["car_version"] = data["vehicle"][i]["car_version"]
            out[vin]["car_type"] = data["vehicle"][i]["car_type"]
            out[vin]["exterior_color"] = data["vehicle"][i]["exterior_color"]
            out[vin]["wheel_type"] = data["vehicle"][i]["wheel_type"]

        return out

    def get_chain_data(self, asset_id, addresses, variable, filter, range_unit,
                       temperature, startdate, enddate):
        """
        Returns vehicle data on chain as specified by 'variable' between start and end dates
        """
        out = {}
        out["x"] = []
        out["y"] = []
        out["z"] = []
        command = "addlistopfromto"
        rec = self.address
        op = self.op_data
        t0 = time.mktime(
            datetime.datetime.strptime(startdate, "%Y-%m-%d").timetuple())
        t1 = time.mktime(
            datetime.datetime.strptime(enddate, "%Y-%m-%d").timetuple())
        t1 = t1 + 24 * 60 * 60  #Enddate Time 23:59:59
        last_month = ""
        last_charge = 0
        sum_monthly = -1
        sum_distance = 0
        sum_charge = 0
        cycle_start = startdate
        cycle_end = enddate

        for sender in addresses.split(","):
            bisdata = self.bismuth.command(command,
                                           [sender, rec, op, 1, False, t0, t1])
            for i in range(0, len(bisdata)):
                data = json.loads(bisdata[i][11])
                try:
                    vin = data["vin"]["0"]
                except:
                    vin = ''

                if len(vin) > 0:
                    if "monthly_" in variable:
                        if vin == asset_id:
                            ts = int(data[vin]["timestamp"]) / 1000
                            month = datetime.datetime.fromtimestamp(
                                ts).strftime("%B %Y")
                            if month != last_month:
                                if sum_monthly != -1:
                                    out["x"].append(last_month)
                                    if "_efficiency" in variable:
                                        if sum_distance > 0:
                                            out["y"].append(
                                                round(1e6 * sum_charge /
                                                      sum_distance) / 1e3)
                                        else:
                                            out["y"].append("Not defined")
                                    elif "_cycles" in variable:
                                        cycle_end = datetime.datetime.fromtimestamp(
                                            ts - 86400).strftime("%Y-%m-%d")
                                        cycle_data = self.get_cycle_data(
                                            asset_id, sender, "battery_level",
                                            0, range_unit, temperature,
                                            cycle_start, cycle_end)
                                        cycle_start = datetime.datetime.fromtimestamp(
                                            ts).strftime("%Y-%m-%d")
                                        out["y"].append(
                                            cycle_data["full_cycle_equivalent"]
                                        )
                                    else:
                                        out["y"].append(
                                            self.data_units(
                                                round(1000 * sum_monthly) /
                                                1000, variable, range_unit,
                                                temperature))
                                else:
                                    last_distance = data[vin]["odometer"]
                                sum_distance = 0
                                sum_charge = 0
                                sum_monthly = 0

                            sum_distance += data[vin][
                                "odometer"] - last_distance
                            if data[vin]["charge_energy_added"] != last_charge:
                                sum_charge += data[vin]["charge_energy_added"]

                            if "monthly_distance" in variable:
                                sum_monthly += data[vin][
                                    "odometer"] - last_distance
                            if "monthly_energy" in variable:
                                if data[vin][
                                        "charge_energy_added"] != last_charge:
                                    sum_monthly += data[vin][
                                        "charge_energy_added"]

                            last_distance = data[vin]["odometer"]
                            last_charge = data[vin]["charge_energy_added"]
                            last_month = datetime.datetime.fromtimestamp(
                                ts).strftime("%B %Y")
                    else:
                        if variable == "max_range_vs_odometer":
                            data[vin][
                                "max_range_vs_odometer"] = self.__get_max_range(
                                    data[vin])
                        if variable == "est_max_range":
                            data[vin]["est_max_range"] = self.__get_max_range(
                                data[vin])
                        ts = int(data[vin]["timestamp"]) / 1000
                        mydate = datetime.datetime.fromtimestamp(ts)
                        try:
                            if vin == asset_id:
                                out["y"].append(
                                    self.data_units(data[vin][variable],
                                                    variable, range_unit,
                                                    temperature))
                                if "_vs_odometer" in variable:
                                    out["x"].append(data[vin]["odometer"])
                                else:
                                    out["x"].append(
                                        f"{mydate:%Y-%m-%d %H:%M:%S}")
                                out["z"] = 0
                        except:
                            pass

        if "monthly_" in variable:
            out["x"].append(
                datetime.datetime.fromtimestamp(ts).strftime("%B %Y"))
            if "_efficiency" in variable:
                if sum_distance > 0:
                    out["y"].append(
                        round(1e6 * sum_charge / sum_distance) / 1e3)
                else:
                    out["y"].append("Not defined")
            elif "_cycles" in variable:
                cycle_end = datetime.datetime.fromtimestamp(
                    ts + 86400).strftime("%Y-%m-%d")
                cycle_data = self.get_cycle_data(asset_id, sender,
                                                 "battery_level", 0,
                                                 range_unit, temperature,
                                                 cycle_start, cycle_end)
                out["y"].append(cycle_data["full_cycle_equivalent"])
            else:
                out["y"].append(
                    self.data_units(
                        round(1000 * sum_monthly) / 1000, variable, range_unit,
                        temperature))

        if variable == "max_range_vs_odometer":
            out["z_x"] = []
            out["z_y"] = []
            if len(out["x"]) > 2:
                w = mp.polyfit(out["x"], out["y"], 2, filter)
                data = mp.interpolate(min(out["x"]), max(out["x"]), 20, w)
                out["z_x"] = data["x"]
                out["z_y"] = data["y"]

        return out

    def get_cycle_data(self, asset_id, addresses, variable, filter, range_unit,
                       temperature, startdate, enddate):
        """
        Returns cycle data on chain as specified by 'variable' between start and end dates
        """
        out = {}
        out["x"] = []
        out["y"] = []
        out["full_cycle_equivalent"] = 0
        data = self.get_chain_data(asset_id, addresses, variable, filter,
                                   range_unit, temperature, startdate, enddate)
        try:
            cycles = rainflow.count_cycles(data["y"], binsize=10.0)
            sum = 0.0
            for i in range(len(cycles)):
                out["x"].append("Cycle {}-{}%".format(cycles[i][0] - 10,
                                                      cycles[i][0]))
                out["y"].append(cycles[i][1])
                sum += self.full_cycle_equivalent(cycles[i])
            out["full_cycle_equivalent"] = round(100 * sum) / 100
        except:
            pass
        return out

    def myparse(self, html, search_string):
        L = len(search_string)
        i = html.find(search_string)
        j = html.find('"', i + L + 1)
        return html[i + L:j]

    def myparse2(self, html, search_string):
        L = len(search_string)
        i = html.find(search_string)
        j = html.find('&', i + L + 1)
        return html[i + L:j]

    def html_parse(self, data, html):
        data['_csrf'] = self.myparse(html, 'name="_csrf" value="')
        data['_phase'] = self.myparse(html, 'name="_phase" value="')
        data['_process'] = self.myparse(html, 'name="_process" value="')
        data['transaction_id'] = self.myparse(html,
                                              'name="transaction_id" value="')
        return data

    def __tesla_connect(self, email):
        """
        Connect to vehicles associated with email address
        """
        self.tesla = Tesla(email)
        self.tesla.fetch_token()

    def tesla_vins(self, email, pwd):
        """
        Returns all VIN numbers associated with specified email
        """
        self.__tesla_connect(email)
        vehicle = self.fetch_vehicle_data(email, pwd)
        out = {}
        L = vehicle["total"]
        out["vehicle"] = {}
        out["count"] = L
        for i in range(0, L):
            vin = vehicle['vin'][str(i)]
            out["vehicle"][str(i)] = vin

        return out

    def tesla_data(self, email):
        """
        Returns selected vehicle data given email.
        If owner has multiple vehicles, data for all of them is returned.
        """
        S = self.__tesla_connect(email)
        out = {}
        out["vehicle"] = {}
        out["count"] = 0

        selected = prod = self.tesla.vehicle_list()
        for i, product in enumerate(selected):
            product.sync_wake_up()
            out["count"] = out["count"] + 1
            data = product.get_vehicle_data()
            battery_type = ""
            try:
                option_codes = data["option_codes"].split(",")
                for j in range(len(option_codes)):
                    if option_codes[j].find("BT") == 0:
                        battery_type = option_codes[j]
                        break
            except:
                pass

            vin = data["vin"]
            out["vehicle"][i] = {}
            out["vehicle"][i]["charge_energy_added"] = data["charge_state"][
                "charge_energy_added"]
            out["vehicle"][i]["charge_miles_added_rated"] = data[
                "charge_state"]["charge_miles_added_rated"]
            out["vehicle"][i]["charge_miles_added_ideal"] = data[
                "charge_state"]["charge_miles_added_ideal"]
            out["vehicle"][i]["est_battery_range"] = data["charge_state"][
                "est_battery_range"]
            out["vehicle"][i]["ideal_battery_range"] = data["charge_state"][
                "ideal_battery_range"]
            out["vehicle"][i]["charge_current_request"] = data["charge_state"][
                "charge_current_request"]
            out["vehicle"][i]["usable_battery_level"] = data["charge_state"][
                "usable_battery_level"]
            out["vehicle"][i]["battery_range"] = data["charge_state"][
                "battery_range"]
            out["vehicle"][i]["battery_level"] = data["charge_state"][
                "battery_level"]
            out["vehicle"][i]["timestamp"] = data["charge_state"]["timestamp"]
            out["vehicle"][i]["odometer"] = data["vehicle_state"]["odometer"]
            out["vehicle"][i]["inside_temp"] = data["climate_state"][
                "inside_temp"]
            out["vehicle"][i]["outside_temp"] = data["climate_state"][
                "outside_temp"]
            out["vehicle"][i]["car_version"] = data["vehicle_state"][
                "car_version"]
            out["vehicle"][i]["battery_type"] = battery_type
            out["vehicle"][i]["car_type"] = data["vehicle_config"]["car_type"]
            out["vehicle"][i]["exterior_color"] = data["vehicle_config"][
                "exterior_color"]
            out["vehicle"][i]["wheel_type"] = data["vehicle_config"][
                "wheel_type"]
            out["vehicle"][i]["vin"] = vin

        return out

    def __decode_char(self, c):
        """
        Transliteration keys, see https://en.wikipedia.org/wiki/Vehicle_identification_number
        """
        options = {
            '1': 1,
            '2': 2,
            '3': 3,
            '4': 4,
            '5': 5,
            '6': 6,
            '7': 7,
            '8': 8,
            '9': 9,
            'A': 1,
            'B': 2,
            'C': 3,
            'D': 4,
            'E': 5,
            'F': 6,
            'G': 7,
            'H': 8,
            'J': 1,
            'K': 2,
            'L': 3,
            'M': 4,
            'N': 5,
            'P': 7,
            'R': 9,
            'S': 2,
            'T': 3,
            'U': 4,
            'V': 5,
            'W': 6,
            'X': 7,
            'Y': 8,
            'Z': 9
        }
        try:
            w = 0
            w = options[c]
        except:
            pass
        return w

    def __weight(self, pos):
        """
        Weight factors, see https://en.wikipedia.org/wiki/Vehicle_identification_number
        """
        options = {
            0: 8,
            1: 7,
            2: 6,
            3: 5,
            4: 4,
            5: 3,
            6: 2,
            7: 10,
            8: 0,
            9: 9,
            10: 8,
            11: 7,
            12: 6,
            13: 5,
            14: 4,
            15: 3,
            16: 2,
            17: 6
        }
        try:
            w = 0
            w = options[pos]
        except:
            pass
        return w

    def checkVIN(self, vin):
        """
        Returns out=1 if specified VIN is valid, out=-1 otherwise.
        """
        out = -1
        try:
            sum = 0
            N = len(vin)
            if N > 10:
                check_in = vin[8]
                for i in range(0, N):
                    sum += self.__weight(i) * self.__decode_char(vin[i])
                compare = sum % 11
                if (compare == 11):
                    check = 'X'
                else:
                    check = str(compare)

                if (check_in == check):
                    out = 1
        except:
            pass

        return out

    def checkID(self, asset_id):
        """
        Returns out=1 if specified asset ID is valid, out=-1 otherwise.
        """
        out = -1
        crc = self.checksum(asset_id, False)
        if asset_id.endswith(crc):
            out = 1
        return out

    def data_units(self, data, var, range_unit, temperature):
        """
        Returns either range or temperature with the specified unit.
        Range or temperature is chosen based on keyword in the string var.
        It is assumed that the input data is by default in either miles or Celsius.
        """
        out = data
        if temperature == "F":
            if var.find("temp") > 0:
                out = round(data * 1.8 + 32.0, 1)

        if range_unit == "km":
            if (var.find("miles") > 0) or (var.find("range") > 0) or \
                (var.find("meter") > 0) or (var.find("distance") > 0):
                out = round(data * 1.609344, 3)

        return out

    def __get_max_range(self, data):
        range = 0
        if data["battery_level"] > 0:
            range = data["battery_range"] / (data["battery_level"] / 100.0)
        range = round(range, 3)
        return range

    def __normalize(self, out):
        y = out["y"]
        M = max(y)
        out["y"] = [x / M for x in y]
        return out

    def encryptDecrypt(self, input, key):
        N = len(key)
        if N > 0:
            out = ""
            M = len(input)
            j = 0
            for i in range(M):
                out = out[:i] + chr(ord(input[i]) ^ ord(key[j]))
                j = j + 1
                if j == N:
                    j = 0
        else:
            out = input
        return out

    def checksum(self, input, lastchar: bool = True):
        if lastchar == True:
            M = len(input)
        else:
            M = len(input) - 1
        out = 0
        for i in range(M):
            out = out + ord(input[i])
        return format(out % 16, "x")

    def full_cycle_equivalent(self, cycle):
        factor = {
            10: 0.043,
            20: 0.086,
            30: 0.155,
            40: 0.225,
            50: 0.400,
            60: 0.500,
            70: 0.600,
            80: 0.700,
            90: 0.850,
            100: 1.0
        }
        return round(100 * factor[cycle[0]] * cycle[1]) / 100.0