From eab5d641e1ad7ac5827e8cf6edd3efec59e1eb92 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Tue, 15 Dec 2015 23:31:38 +0200 Subject: [PATCH 01/69] Rewrite using HIDAPI and Click --- README.rst | 8 +- evic/__init__.py | 7 +- evic/binfile.py | 41 +++--- evic/cli.py | 329 +++++++++++++++++++++++++--------------------- evic/dataflash.py | 118 +++++++++++++++++ evic/device.py | 230 ++++++++++++++++---------------- evic/helpers.py | 13 +- setup.py | 13 +- 8 files changed, 456 insertions(+), 303 deletions(-) create mode 100644 evic/dataflash.py diff --git a/README.rst b/README.rst index c474025..75465d8 100644 --- a/README.rst +++ b/README.rst @@ -2,8 +2,7 @@ Evic =============================== - -Evic decrypts/encrypts Joyetech Evic firmware images and uploads them using USB. +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. Supported devices --------------------- @@ -18,7 +17,10 @@ Tested firmware versions * Evic VTC Mini 1.20 * Evic VTC Mini 1.30 * Evic VTC Mini 2.00 -* Presa TC75W 1.02 +* Presa TC75W 1.02\* + +\*Flashing Presa firmware to a VTC Mini requires changing the hardware version +on some devices. Backup your data flash before flashing! Installation ------------- diff --git a/evic/__init__.py b/evic/__init__.py index f04c47b..44da879 100644 --- a/evic/__init__.py +++ b/evic/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Evic decrypts/encrypts Joyetech Evic firmware images and uploads them using USB. +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. Copyright © Jussi Timperi This program is free software: you can redistribute it and/or modify @@ -20,6 +20,7 @@ __version__ = '0.1.dev0' -from .device import VTCMini, DataFlash +from .device import DEVICE_NAMES, VTCMini from .helpers import cal_checksum -from .binfile import BinFile, FirmwareException +from .binfile import BinFile, FirmwareError +from .dataflash import DataFlash, DataFlashError diff --git a/evic/binfile.py b/evic/binfile.py index 7b5b7a5..563e283 100644 --- a/evic/binfile.py +++ b/evic/binfile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Evic decrypts/encrypts Joyetech Evic firmware images and uploads them using USB. +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. Copyright © Jussi Timperi This program is free software: you can redistribute it and/or modify @@ -18,39 +18,40 @@ """ -class FirmwareException(Exception): - """Exception for firmware verification""" +class FirmwareError(Exception): + """Firmware verification error.""" pass class BinFile(object): - """Firmware binary file + """Firmware binary file class Attributes: - data: binary data of the firmware image - + data: A bytearray containing binary data of the firmware. """ + def __init__(self, data): self.data = bytearray(data) @staticmethod def _genfun(filesize, index): - """Generator function for decrypting/encrypting the binary file + """Generator function for decrypting/encrypting the binary file. Args: - filesize: An integer, filesize of the binary file - index: An integer, index of the byte that is being decrypted - + filesize: An integer, filesize of the binary file. + index: An integer, index of the byte that is being decrypted. """ + return filesize + 408376 + index - filesize // 408376 def convert(self): - """ Decrypts/Encrypts the binary data. + """Decrypts/Encrypts the binary data. Returns: - A Bytearray containing decrypted/encrypted APROM image + A Bytearray containing decrypted/encrypted APROM image. """ + data = bytearray(len(self.data)) for i in range(0, len(self.data)): data[i] = (self.data[i] ^ @@ -58,19 +59,19 @@ def convert(self): return data def verify(self, product_names): - """Verifies that the unencrypted APROM is correct + """Verifies the data unencrypted firmware. Args: - product_names: A list of supported product names for the device + product_names: A list of supported product names for the device. Raises: FirmwareException: Verification failed. """ if b'Joyetech APROM' not in self.data: - raise FirmwareException( - "Firmware manufacturer verification failed") - for name in product_names: - if name in self.data: - return - raise FirmwareException("Firmware device name verification failed") + raise FirmwareError( + "Firmware manufacturer verification failed.") + if not any(product_name in self.data for product_name in + product_names): + raise FirmwareError("Firmware device name verification failed.") + diff --git a/evic/cli.py b/evic/cli.py index fe64790..30a77e6 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Evic decrypts/encryps Joyetech Evic firmware images and uploads them using USB. +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. Copyright © Jussi Timperi This program is free software: you can redistribute it and/or modify @@ -20,166 +20,199 @@ import sys import struct -import argparse from time import sleep -from array import array -import usb.core +import click import evic -DEVICE_NAMES = {b'E052': "eVic-VTC Mini", b'W007': "Presa TC75W"} +class Context(object): + """Click context. + Attributes: + device_names: A dictionary mapping of device names. + dev: An instance of evic.VTCMini. + """ + + def __init__(self): + self.dev = evic.VTCMini() + +pass_context = click.make_pass_decorator(Context, ensure=True) + + +@click.group() def main(): - """Console application's main entry point""" - - parser = argparse.ArgumentParser() - subparsers = parser.add_subparsers() - - parser_upload = subparsers.add_parser('upload', - help='Write firmware from INPUT \ - into device.') - parser_upload.add_argument('input', type=argparse.FileType('rb')) - parser_upload.add_argument('--unencrypted', '-u', action='store_true', - help='Use unencrypted firmware image.') - parser_upload.add_argument('--dataflash', '-d', - type=argparse.FileType('rb'), - help='Use data flash file insted the one on \ - the device') - parser_upload.set_defaults(which='upload') - - parser_convert = subparsers.add_parser('convert', - help='Decrypt/Encrypt firmware \ - from INPUT to OUTPUT.') - parser_convert.add_argument('input', type=argparse.FileType('rb')) - parser_convert.add_argument('--output', '-o', type=argparse.FileType('wb'), - required=True) - parser_convert.set_defaults(which='convert') - - parser_dumpdataflash = subparsers.add_parser('dump-dataflash', - help='Dump dataflash \ - to OUTPUT') - parser_dumpdataflash.add_argument('--output', '-o', - type=argparse.FileType('wb'), - required=True) - parser_dumpdataflash.set_defaults(which='dump-dataflash') - - if len(sys.argv) == 1: - parser.print_help() - sys.exit() - args = parser.parse_args() - - dev = evic.VTCMini() - if args.which in ['upload', 'convert']: - binfile = evic.BinFile(args.input.read()) - - if args.which == 'convert' or not args.unencrypted: - aprom = evic.BinFile(binfile.convert()) - else: - aprom = binfile + """A USB programmer for devices based on the Joyetech Evic VTC Mini.""" + + pass - if args.which == 'convert': - try: - args.output.write(aprom.data) - except IOError: - print("Error: Can't write converted file.") - sys.exit() + +def attach(dev): + """Attaches the USB device. + + Attaches the device and prints the device information to the screen. + + Args: + dev: An instance of the device. + """ try: dev.attach() + click.echo("\nFound device:") + click.echo("\tManufacturer: ", nl=False) + click.secho(dev.manufacturer, bold=True) + click.echo("\tProduct: ", nl=False) + click.secho(dev.product, bold=True) + click.echo("\tSerial No: ", nl=False) + click.secho(dev.serial, bold=True) + click.echo("") + except IOError as error: + click.echo("Device not found: " + str(error)) + sys.exit(1) + + +def read_data_flash(dev): + """Reads the data flash from the device. + + Reads the data flash from the device and verifies it. - print("\nFound device:") - print("\tManufacturer: {0}".format(dev.device.manufacturer)) - print("\tProduct: {0}".format(dev.device.product)) - print("\tSerial No: {0}\n".format(dev.device.serial_number)) - - if args.which == 'upload': - try: - aprom.verify(dev.supported_device_names) - except evic.FirmwareException as error: - print(error) - sys.exit() - - print("Reading data flash...\n") - data_flash = evic.DataFlash(dev.get_sys_data()) - ldrom = data_flash.fw_version == 0 - - if args.which == 'upload' and args.dataflash: - df_file = array('B') - df_file.fromfile(args.dataflash, 2048) - data_flash = evic.DataFlash(df_file) - - if data_flash.device_name in DEVICE_NAMES: - devicename = DEVICE_NAMES[data_flash.device_name] + Args: + dev: An instance of the attached device. + """ + + try: + click.echo("Reading data flash...", nl=False) + dev.get_sys_data() + if struct.unpack("=I", dev.data_flash.data[264:268])[0] \ + or not dev.data_flash.fw_version: + dev.get_sys_data() + click.secho("OK", fg='green', bold=True) + click.echo("Verifying data flash...", nl=False) + dev.data_flash.verify() + click.secho("OK", fg='green', bold=True) + + if dev.data_flash.device_name in evic.DEVICE_NAMES: + devicename = evic.DEVICE_NAMES[dev.data_flash.device_name] else: devicename = "Unknown device" - print("\tDevice name: {0}".format(devicename)) - print("\tFirmware version: {0:.2f}".format(data_flash.fw_version)) - print("\tHardware version: {0:.2f}\n".format( - data_flash.hw_version / 100.0)) - - if evic.cal_checksum(data_flash.data[4:]) == data_flash.checksum \ - and data_flash.checksum \ - | struct.unpack("=I", data_flash.data[268:268+4])[0]: - if data_flash.hw_version > 1000: - print("Please set the hardware version.\n") - - if struct.unpack("=I", data_flash.data[264:264+4]) == 0 \ - or not data_flash.fw_version: - if args.which == 'upload' and not args.dataflash: - print("Reading data flash...\n") - data_flash = evic.DataFlash(dev.get_sys_data()) - - if args.which == 'dump-dataflash': - try: - print("Writing data flash to the file...\n") - args.output.write(data_flash.data) - except IOError: - print("Error: Can't write data flash file.") - sys.exit() - - new_df = data_flash.data - # Bootflag? - # 0 = APROM - # 1 = LDROm - new_df[13] = 1 - - # Flashing Presa firmware requires HW version 1.03 - if b'W007' in aprom.data and data_flash.device_name == b'E052' \ - and data_flash.hw_version in [106, 108, 109, 111]: - print("Changing HW version to 1.03...\n") - new_hw_version = bytearray(struct.pack("=I", 103)) - for i in range(4): - new_df[8+i] = new_hw_version[i] - - # Calculate new checksum - checksum = bytearray(struct.pack("=I", - evic.cal_checksum(new_df[4:]))) - for i in range(4): - new_df[i] = checksum[i] - - data_flash = evic.DataFlash(new_df) - - print("Writing data flash...\n") - sleep(2) - dev.set_sys_data(data_flash) - if not ldrom: - print("Restarting the device...\n") - try: - dev.reset_system() - except usb.core.USBError: - print("Restart failed. Assuming the device is already \ - restarted to LDROM\n") - sleep(2) - print("Reconnecting the device...\n") - dev.attach() - - print("Uploading APROM...\n") - dev.upload_aprom(aprom) - print("Firmware upload complete!") - - except AssertionError as error: - print(error) - sys.exit() + click.echo("\n\tDevice name: ", nl=False) + click.secho(devicename, bold=True) + click.echo("\tFirmware version: ", nl=False) + click.secho("{0:.2f}".format( + dev.data_flash.fw_version / 100.0), bold=True) + click.echo("\tHardware version: ", nl=False) + click.secho("{0:.2f}\n".format( + dev.data_flash.hw_version / 100.0), bold=True) + + if dev.data_flash.hw_version > 1000: + click.echo("Please set the hardware version.") + + except (IOError, evic.DataFlashError) as error: + click.secho("FAIL", fg='red', bold=True) + click.echo(error) + sys.exit(1) + + +@main.command() +@click.argument('input', type=click.File('rb')) +@click.option('--unencrypted/--encrypted', '-u/-e', default=False, + help='Use unencrypted/encrypted image. Defaults to encrypted.') +@click.option('--dataflash', '-d', type=click.File('rb'), + help='Use data flash from a file.') +@pass_context +def upload(ctx, input, unencrypted, dataflash): + """Upload an APROM image to the device.""" + + attach(ctx.dev) + read_data_flash(ctx.dev) + + binfile = evic.BinFile(input.read()) + if unencrypted: + aprom = binfile + else: + aprom = evic.BinFile(binfile.convert()) + + try: + click.echo("Verifying APROM...", nl=False) + aprom.verify(ctx.dev.supported_device_names) + click.secho("OK", fg='green', bold=True) + except evic.FirmwareError as error: + click.secho("FAIL", fg='red', bold=True) + click.echo(error) + sys.exit(1) + + if dataflash: + data_flash_file = evic.DataFlash(dataflash.read()) + try: + click.echo("Verifying data flash file...", nl=False) + data_flash_file.verify() + click.secho("OK", fg='green', bold=True) + data_flash = data_flash_file + except evic.DataFlashError as error: + click.secho("FAIL", fg='red', bold=True) + click.echo(error) + sys.exit(1) + else: + data_flash = ctx.dev.data_flash + + data_flash.bootflag = 1 + + # Flashing Presa firmware requires HW version <=1.03 on type A devices + if b'W007' in aprom.data and data_flash.device_name == b'E052' \ + and data_flash.hw_version in [106, 108, 109, 111]: + click.echo("Changing HW version to 1.03...", nl=False) + data_flash.hw_version = 103 + click.secho("OK", fg='green', bold=True) + + click.echo("Writing data flash...", nl=False) + ctx.dev.set_sys_data(data_flash) + click.secho("OK", fg='green', bold=True) + if not ctx.dev.ldrom: + click.echo("Restarting the device...", nl=False) + ctx.dev.reset_system() + sleep(2) + click.secho("OK", fg='green', bold=True) + click.echo("Reconnecting the device...", nl=False) + ctx.dev.attach() + click.secho("OK", fg='green', bold=True) + + click.echo("Writing APROM...", nl=False) + ctx.dev.upload_aprom(aprom) + click.secho("OK", fg='green', bold=True) + + +@main.command('dump-dataflash') +@click.option('--output', '-o', type=click.File('wb')) +@pass_context +def dumpdataflash(ctx, output): + """Write device data flash to a file.""" + + attach(ctx.dev) + read_data_flash(ctx.dev) + + try: + click.echo("Writing data flash to the file...", nl=False) + output.write(ctx.dev.data_flash.data) + click.secho("OK", fg='green', bold=True) + except IOError as error: + click.secho("FAIL", fg='red', bold=True) + click.echo("Error: Can't write data flash file: " + str(error)) + sys.exit(1) + + +@main.command() +@click.argument('input', type=click.File('rb')) +@click.option('--output', '-o', type=click.File('wb')) +def convert(input, output): + """Decrypt/encrypt an APROM image.""" + + infile = evic.BinFile(input.read()) + outfile = evic.BinFile(infile.convert()) + + try: + output.write(outfile.data) + except IOError: + click.echo("Error: Can't write converted file.") + sys.exit(1) diff --git a/evic/dataflash.py b/evic/dataflash.py new file mode 100644 index 0000000..d97328d --- /dev/null +++ b/evic/dataflash.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +""" +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. +Copyright © Jussi Timperi + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +import struct + +from .helpers import cal_checksum + + +class DataFlashError(Exception): + """Data flash verification error.""" + + pass + + +class DataFlash(object): + """Device data flash class. + + Attributes: + data: A bytearray containing binary data of the data flash. + device_name: A bytestring containing device name. + hw_version: An integer hardware version. + fw_version: An integer firmware version. + bootflag: 0 or 1. Controls whether APROM or LDROM is booted + when the device is restarted. TODO: Confirm this. + 0 = LDROM + 1 = APROM + checksum: A bytearray containing checksum for the data flash. + """ + + def __init__(self, data): + self.data = bytearray(data) + self._device_name = bytes(self.data[316:320]) + self._hw_version = struct.unpack("=I", self.data[8:12])[0] + self._fw_version = struct.unpack("=I", self.data[260:264])[0] + self._bootflag = self.data[13] + self._checksum = self.data[0:4] + + @property + def device_name(self): + return self._device_name + + @device_name.setter + def device_name(self, device_name): + self.device_name = device_name + self.data[316:320] = bytearray(struct.pack("4s", device_name)) + self.update_checksum() + + @property + def hw_version(self): + return self._hw_version + + @hw_version.setter + def hw_version(self, version): + self._hw_version = version + self.data[8:12] = bytearray(struct.pack("=I", version)) + self.update_checksum() + + @property + def fw_version(self): + return self._fw_version + + @fw_version.setter + def fw_version(self, version): + self._fw_version = version + self.data[260:264] = bytearray(struct.pack("=I", version)) + self.update_checksum() + + @property + def bootflag(self): + return self._bootflag + + @bootflag.setter + def bootflag(self, flag): + self._bootflag = flag + self.data[13] = flag + self.update_checksum() + + @property + def checksum(self): + return self._checksum + + @checksum.setter + def checksum(self, checksum): + self._checksum = checksum + self.data[0:4] = checksum + + def update_checksum(self): + """Updates the checksum for the data flash data.""" + + self.checksum = cal_checksum(self.data[4:]) + + def verify(self): + """Verifies the data flash. + + Raises: + DataFlashError: Data flash verification failed. + """ + + if cal_checksum(self.data[4:]) != self.checksum \ + or not struct.unpack("=I", self.checksum)[0] \ + | struct.unpack("=I", self.data[268:272])[0]: + raise DataFlashError("Data flash verification failed") diff --git a/evic/device.py b/evic/device.py index e8bf203..70b4b1c 100644 --- a/evic/device.py +++ b/evic/device.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Evic decrypts/encrypts Joyetech Evic firmware images and uploads them using USB. +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. Copyright © Jussi Timperi This program is free software: you can redistribute it and/or modify @@ -19,14 +19,16 @@ import struct -import usb.core -import usb.util +import hid +from .dataflash import DataFlash from .helpers import cal_checksum +DEVICE_NAMES = {b'E052': "eVic-VTC Mini", b'W007': "Presa TC75W"} -class Cmd(object): - """Nuvoton HID command class + +class HIDCmd(object): + """Nuvoton HID command class. Available HID command codes: 0x35: Read data flash. @@ -35,49 +37,37 @@ class Cmd(object): 0xC3: Write APROM. Attributes: - cmd: A bytearray object for HID command code (1 byte) - length: A bytearray object for HID command length (1 byte) - arg1: A bytearray object for the first HID command argument (4 bytes) - arg2: A bytearray object for the second HID command argument (4 bytes) - signature: A bytearray object for HID command signature (4 bytes) - checksum: A bytearray object for the checksum of the HID command - (4 bytes) - fullcmd: A bytearray object for the full command (18 bytes) + cmdcode: A list containing HID command code (1 byte). + length: A list containing HID command length, + not including the checksum (1 byte). + arg1: A list containing the first HID command argument (4 bytes). + arg2: A list containing the second HID command argument (4 bytes). + signature: A list containing the HID command signature (4 bytes). + checksum: A list containing the checksum of the HID command. + (4 bytes). + cmd: A list containing the full command (18 bytes). """ - signature = bytearray(struct.pack('=I', 0x43444948)) + signature = [byte for byte in bytearray(struct.pack('=I', 0x43444948))] # Do not count the last 4 bytes (checksum) - length = bytearray(struct.pack('=B', 14)) - - def __init__(self, cmd, arg1, arg2): - self.cmd = bytearray(struct.pack('=B', cmd)) - self.arg1 = bytearray(struct.pack('=I', arg1)) - self.arg2 = bytearray(struct.pack('=I', arg2)) - self.fullcmd = self.cmd + self.length + self.arg1 + self.arg2 +\ - self.signature - self.checksum = bytearray(struct.pack('=I', - cal_checksum(self.fullcmd))) - self.fullcmd += self.checksum + length = [14] + def __init__(self, cmdcode, arg1, arg2): + self.cmdcode = [byte for byte in bytearray(struct.pack('=B', cmdcode))] + self.arg1 = [byte for byte in bytearray(struct.pack('=I', arg1))] + self.arg2 = [byte for byte in bytearray(struct.pack('=I', arg2))] -class DataFlash(object): - """ Device data flash class - - Attributes: - data: An array containing binary data of the data flash - device_name: A bytestring containing device name. - hw_version: An integer hardware version. - fw_version: A float firmware version. - checksum: An integer checksum for data flash. + @property + def cmd(self): + """HID Command - """ + Returns: + A list containing the full HID command + """ - def __init__(self, df): - self.data = df - self.device_name = df[316:316+4].tostring() - self.hw_version = struct.unpack("=I", df[8:8+4])[0] - self.fw_version = struct.unpack("=I", df[260:260+4])[0] / 100.0 - self.checksum = struct.unpack("=I", df[0:4])[0] + cmd = self.cmdcode + self.length + self.arg1 + self.arg2 + \ + self.signature + return cmd + [byte for byte in cal_checksum(cmd)] class VTCMini(object): @@ -88,8 +78,12 @@ class VTCMini(object): pid = USB product ID as an integer. supported_device_names: A list of bytestrings containing the name of the product with compatible firmware - device: PyUSB device for the VTC Mini. - + device: A HIDAPI device for the VTC Mini. + manufacturer: A string containing the device manufacturer. + product: A string containing the product name. + serial: A string conraining the product serial number. + data_flash: An instance of DataFlash containing the device data flash. + ldrom: A Boolean value set to True if the device is booted to LDROM. """ vid = 0x0416 @@ -97,128 +91,124 @@ class VTCMini(object): supported_device_names = [b'E052', b'W007'] def __init__(self): - self.device = None + self.device = hid.device() + self.manufacturer = None + self.product = None + self.serial = None + self.data_flash = None + self.ldrom = False def attach(self): - """Detaches kernel drivers from the device and claims it - - Raises: - AssertionError: If device could not be opened. + """Opens the USB device. + Opens the device and retrieves the device attributes. """ - self.device = usb.core.find(idVendor=self.vid, idProduct=self.pid) - assert self.device, "Device not found" - try: - if self.device.is_kernel_driver_active(0): - self.device.detach_kernel_driver(0) - self.device.set_configuration() - usb.util.claim_interface(self.device, 0) - except NotImplementedError: - pass - - def send_cmd(self, cmd): - """Sends a HID command - Writes a HID command to the device. + self.device.open(self.vid, self.pid) + self.manufacturer = self.device.get_manufacturer_string() + self.product = self.device.get_product_string() + self.serial = self.device.get_serial_number_string() - Args: - cmd: A bytearray object for the HID command in the form of - Cmd.fullcommand + def get_sys_data(self): + """Reads the device data flash - Returns: - An integer count of bytes written to the device + Writes the HID command for reading the data flash + and retrieves the data flash to the data_flash attribute as an instance + of DataFlash. + Sets the ldrom attribute to True if the device is booted to LDROM. """ - return self.device.write(0x2, cmd, 1000) - def get_sys_data(self): - """Sends the HID command for reading data flash (0x35) + start = 0 + end = 2048 - Writes the HID command to the device and returns the data flash from - the device. + read_df = HIDCmd(0x35, start, end) + self.write(read_df.cmd) - Returns: - An array containing the binary data of the data flash. + self.data_flash = DataFlash(self.read(end)) + + self.ldrom = self.data_flash.fw_version == 0 + + def write(self, data): + """Writes data to the device. + + Args: + data: A list containing binary data. Raises: - AssertionError: Correct amount of bytes was not written to the - device. (18 bytes) + IOError: Incorrect amount of bytes was written. """ - start = 0 - end = 2048 - - cmd = Cmd(0x35, start, end) - assert self.send_cmd(cmd.fullcmd) == 18,\ - "Error: Sending read data flash command failed." + bytes_written = 0 + chunks = [data[i:i+64] for i in range(0, len(data), 64)] + for chunk in chunks: + buf = [0] + chunk + bytes_written += self.device.write(buf) - 1 - return self.read_data(end) + if bytes_written != len(data): + raise IOError("HID Write failed.") - def read_data(self, count): - """Reads data from the device + def read(self, length): + """Reads data from the device. Args: - count: An integer, count of bytes to read. + length: Amount of bytes bytes to read. Returns: - An array object of the data read. + A list containing binary data. Raises: - AssertionError: Incorrect amount of bytes was read. + IOError: Incorrect amount of bytes was read. """ - data = self.device.read(0x81, count) - assert len(data) == count, 'Error: Read failed' - return data - def set_sys_data(self, df): - """Sends the HID command for writing data flash (0x53) + data = [] + pages, rem = divmod(length, 64) + for _ in range(0, pages): + data += self.device.read(64) + if rem: + data += self.device.read(rem) - Writes the HID command to the device and writes 2048 bytes from - the df argument to the device data flash. + if len(data) != length: + raise IOError("HID read failed") - Args: - df: A DataFlash object containing the data flash data + return data - Raises: - AssertionError: Incorrect amount of bytes was written. + def set_sys_data(self, data_flash): + """Writes the device data flash. + + Writes the HID command for writing the data flash + and writes the first 2048 bytes from the data_flash argument + to the device data flash. + Args: + data_flash: A DataFlash object. """ start = 0 end = 2048 - cmd = Cmd(0x53, start, end) + write_df = HIDCmd(0x53, start, end) + self.write(write_df.cmd) - assert self.send_cmd(cmd.fullcmd) == 18,\ - "Error: Sending write data flash command failed." - - assert self.device.write(0x2, df.data, 100000) == 2048,\ - "Error: Writing data flash failed" + self.write(list(data_flash.data)) def reset_system(self): - """Sends the HID command for reseting the system (0xB4) + """Sends the HID command for resetting the system (0xB4) """ - cmd = Cmd(0xB4, 0, 0) - - assert self.send_cmd(cmd.fullcmd) == 18,\ - "Error: Sending reset command failed." + reset = HIDCmd(0xB4, 0, 0) + self.write(reset.cmd) def upload_aprom(self, aprom): - """Writes APROM to the the device. (0xC3) + """Writes APROM to the the device. Args: - aprom: A BinFile object containing unencrypted APROM image - - Raises: - AssertionError: Incorrect amount of bytes was written. - + aprom: A BinFile object containing unencrypted APROM image. """ + start = 0 end = len(aprom.data) - cmd = Cmd(0xC3, start, end) - assert self.send_cmd(cmd.fullcmd) == 18,\ - "Error: Sending write APROM command failed." + write_aprom = HIDCmd(0xC3, start, end) + self.write(write_aprom.cmd) - assert self.device.write(0x2, aprom.data, 1000000) == len(aprom.data),\ - "Error: APROM write failed" + self.write(list(aprom.data)) diff --git a/evic/helpers.py b/evic/helpers.py index fb7a87f..1e77cfd 100644 --- a/evic/helpers.py +++ b/evic/helpers.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Evic decrypts/encrypts Joyetech Evic firmware images and uploads them using USB. +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. Copyright © Jussi Timperi This program is free software: you can redistribute it and/or modify @@ -17,12 +17,17 @@ along with this program. If not, see . """ +import struct + def cal_checksum(data): - """Calculates a checksum for the data + """Calculates a checksum for the data. Args: - data: An iterable. + data: An iterable that can be summed. + Returns: + A bytearray containing 4 byte integer. """ - return sum(data) + + return bytearray(struct.pack("=I", sum(data))) diff --git a/setup.py b/setup.py index 3a8125f..a1f663a 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -Evic decrypts/encrypts Joyetech Evic firmware images and uploads them using USB. +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. Copyright © Jussi Timperi This program is free software: you can redistribute it and/or modify @@ -27,7 +27,8 @@ readme = readme_file.read() requirements = [ - 'pyusb' + 'hidapi>=0.7.99.post8', + 'click' ] setup( @@ -35,9 +36,9 @@ version="0.1", author="Jussi Timperi", author_email="jussi.timperi@iki.fi", - description=("A tool to decrypt/encrypt and USB upload Evic firmware."), + description=("Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini."), license="GPL", - keywords="ecig electronic cigarette evic joyetech", + keywords="ecig electronic cigarette evic joyetech presa", url="https://github.com/Ban3/python-evic", packages=['evic'], install_requires=requirements, @@ -50,8 +51,10 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', ], entry_points={ - 'console_scripts': ['evic=evic.cli:main'], + 'console_scripts': [ + 'evic=evic.cli:main'], }, ) From 194c93c74c19b9d1aba26ec8431b29b1d0b4a6ea Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Wed, 16 Dec 2015 01:40:47 +0200 Subject: [PATCH 02/69] Fix dependency version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a1f663a..bcffffa 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ readme = readme_file.read() requirements = [ - 'hidapi>=0.7.99.post8', + 'hidapi>=0.7.99', 'click' ] From 2a1809a1d6dec8b11bbe9dd05b849a5e14f9290a Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Wed, 16 Dec 2015 06:31:41 +0200 Subject: [PATCH 03/69] Handle amount of bytes read/written in Windows --- evic/device.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/evic/device.py b/evic/device.py index 70b4b1c..b3a8abe 100644 --- a/evic/device.py +++ b/evic/device.py @@ -144,6 +144,10 @@ def write(self, data): buf = [0] + chunk bytes_written += self.device.write(buf) - 1 + # Windows always writes full pages + if bytes_written > len(data): + bytes_written -= 64 - (len(data) % 64) + if bytes_written != len(data): raise IOError("HID Write failed.") @@ -167,6 +171,10 @@ def read(self, length): if rem: data += self.device.read(rem) + # Windows always reads full pages + if len(data) > length: + data = data[:length] + if len(data) != length: raise IOError("HID read failed") From 667e07f071cc2e92bc42c24d081b2658e635f1f9 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Wed, 16 Dec 2015 06:40:13 +0200 Subject: [PATCH 04/69] Catch exception when writing the APROM fails --- evic/cli.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/evic/cli.py b/evic/cli.py index 30a77e6..53f592c 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -178,9 +178,14 @@ def upload(ctx, input, unencrypted, dataflash): ctx.dev.attach() click.secho("OK", fg='green', bold=True) - click.echo("Writing APROM...", nl=False) - ctx.dev.upload_aprom(aprom) - click.secho("OK", fg='green', bold=True) + try: + click.echo("Writing APROM...", nl=False) + ctx.dev.upload_aprom(aprom) + click.secho("OK", fg='green', bold=True) + except IOError as error: + click.secho("FAIL", fg='red', bold=True) + click.echo(error) + sys.exit(1) @main.command('dump-dataflash') From 1fdf98fb4a3ff9bfa26e955c3df0ac816a886dc3 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Wed, 16 Dec 2015 14:21:07 +0200 Subject: [PATCH 05/69] Use context manager for handling exceptions --- evic/cli.py | 152 ++++++++++++++++++++++++---------------------------- 1 file changed, 71 insertions(+), 81 deletions(-) diff --git a/evic/cli.py b/evic/cli.py index 53f592c..6240d4a 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -21,6 +21,7 @@ import sys import struct from time import sleep +from contextlib import contextmanager import click @@ -41,6 +42,19 @@ def __init__(self): pass_context = click.make_pass_decorator(Context, ensure=True) +@contextmanager +def handle_exceptions(*exceptions): + """Context for handling exceptions.""" + + try: + yield + click.secho("OK", fg='green', bold=True) + except exceptions as error: + click.secho("FAIL", fg='red', bold=True) + click.echo(str(error), err=True) + sys.exit(1) + + @click.group() def main(): """A USB programmer for devices based on the Joyetech Evic VTC Mini.""" @@ -48,7 +62,7 @@ def main(): pass -def attach(dev): +def find_dev(dev): """Attaches the USB device. Attaches the device and prints the device information to the screen. @@ -57,62 +71,60 @@ def attach(dev): dev: An instance of the device. """ - try: + with handle_exceptions(IOError): + click.echo("\nFinding device...", nl=False) dev.attach() - click.echo("\nFound device:") - click.echo("\tManufacturer: ", nl=False) - click.secho(dev.manufacturer, bold=True) - click.echo("\tProduct: ", nl=False) - click.secho(dev.product, bold=True) - click.echo("\tSerial No: ", nl=False) - click.secho(dev.serial, bold=True) - click.echo("") - except IOError as error: - click.echo("Device not found: " + str(error)) - sys.exit(1) + + click.echo("\tManufacturer: ", nl=False) + click.secho(dev.manufacturer, bold=True) + click.echo("\tProduct: ", nl=False) + click.secho(dev.product, bold=True) + click.echo("\tSerial No: ", nl=False) + click.secho(dev.serial, bold=True) + click.echo("") def read_data_flash(dev): """Reads the data flash from the device. - Reads the data flash from the device and verifies it. - Args: dev: An instance of the attached device. """ - try: + with handle_exceptions(IOError): click.echo("Reading data flash...", nl=False) dev.get_sys_data() if struct.unpack("=I", dev.data_flash.data[264:268])[0] \ or not dev.data_flash.fw_version: dev.get_sys_data() - click.secho("OK", fg='green', bold=True) - click.echo("Verifying data flash...", nl=False) - dev.data_flash.verify() - click.secho("OK", fg='green', bold=True) - if dev.data_flash.device_name in evic.DEVICE_NAMES: - devicename = evic.DEVICE_NAMES[dev.data_flash.device_name] - else: - devicename = "Unknown device" + if dev.data_flash.device_name in evic.DEVICE_NAMES: + devicename = evic.DEVICE_NAMES[dev.data_flash.device_name] + else: + devicename = "Unknown device" + + click.echo("\tDevice name: ", nl=False) + click.secho(devicename, bold=True) + click.echo("\tFirmware version: ", nl=False) + click.secho("{0:.2f}".format(dev.data_flash.fw_version / 100.0), bold=True) + click.echo("\tHardware version: ", nl=False) + click.secho("{0:.2f}\n".format( + dev.data_flash.hw_version / 100.0), bold=True) - click.echo("\n\tDevice name: ", nl=False) - click.secho(devicename, bold=True) - click.echo("\tFirmware version: ", nl=False) - click.secho("{0:.2f}".format( - dev.data_flash.fw_version / 100.0), bold=True) - click.echo("\tHardware version: ", nl=False) - click.secho("{0:.2f}\n".format( - dev.data_flash.hw_version / 100.0), bold=True) + if dev.data_flash.hw_version > 1000: + click.echo("Please set the hardware version.") - if dev.data_flash.hw_version > 1000: - click.echo("Please set the hardware version.") - except (IOError, evic.DataFlashError) as error: - click.secho("FAIL", fg='red', bold=True) - click.echo(error) - sys.exit(1) +def verify_dataflash(data_flash): + """Verifies that the data flash is correct. + + Args: + data_flash: An instance of evic.DataFlash. + """ + + with handle_exceptions(evic.DataFlashError): + click.echo("Verifying data flash...", nl=False) + data_flash.verify() @main.command() @@ -125,8 +137,9 @@ def read_data_flash(dev): def upload(ctx, input, unencrypted, dataflash): """Upload an APROM image to the device.""" - attach(ctx.dev) + find_dev(ctx.dev) read_data_flash(ctx.dev) + verify_dataflash(ctx.dev.data_flash) binfile = evic.BinFile(input.read()) if unencrypted: @@ -134,26 +147,14 @@ def upload(ctx, input, unencrypted, dataflash): else: aprom = evic.BinFile(binfile.convert()) - try: + with handle_exceptions(evic.FirmwareError): click.echo("Verifying APROM...", nl=False) aprom.verify(ctx.dev.supported_device_names) - click.secho("OK", fg='green', bold=True) - except evic.FirmwareError as error: - click.secho("FAIL", fg='red', bold=True) - click.echo(error) - sys.exit(1) if dataflash: data_flash_file = evic.DataFlash(dataflash.read()) - try: - click.echo("Verifying data flash file...", nl=False) - data_flash_file.verify() - click.secho("OK", fg='green', bold=True) - data_flash = data_flash_file - except evic.DataFlashError as error: - click.secho("FAIL", fg='red', bold=True) - click.echo(error) - sys.exit(1) + verify_dataflash(data_flash_file) + data_flash = data_flash_file else: data_flash = ctx.dev.data_flash @@ -166,26 +167,21 @@ def upload(ctx, input, unencrypted, dataflash): data_flash.hw_version = 103 click.secho("OK", fg='green', bold=True) - click.echo("Writing data flash...", nl=False) - ctx.dev.set_sys_data(data_flash) - click.secho("OK", fg='green', bold=True) - if not ctx.dev.ldrom: - click.echo("Restarting the device...", nl=False) - ctx.dev.reset_system() - sleep(2) - click.secho("OK", fg='green', bold=True) - click.echo("Reconnecting the device...", nl=False) - ctx.dev.attach() + with handle_exceptions(IOError): + click.echo("Writing data flash...", nl=False) + ctx.dev.set_sys_data(data_flash) click.secho("OK", fg='green', bold=True) + if not ctx.dev.ldrom: + click.echo("Restarting the device...", nl=False) + ctx.dev.reset_system() + sleep(2) + click.secho("OK", fg='green', bold=True) + click.echo("Reconnecting the device...", nl=False) + ctx.dev.attach() + click.secho("OK", fg='green', bold=True) - try: click.echo("Writing APROM...", nl=False) ctx.dev.upload_aprom(aprom) - click.secho("OK", fg='green', bold=True) - except IOError as error: - click.secho("FAIL", fg='red', bold=True) - click.echo(error) - sys.exit(1) @main.command('dump-dataflash') @@ -194,17 +190,13 @@ def upload(ctx, input, unencrypted, dataflash): def dumpdataflash(ctx, output): """Write device data flash to a file.""" - attach(ctx.dev) + find_dev(ctx.dev) read_data_flash(ctx.dev) + verify_dataflash(ctx.dev.data_flash) - try: + with handle_exceptions(IOError): click.echo("Writing data flash to the file...", nl=False) output.write(ctx.dev.data_flash.data) - click.secho("OK", fg='green', bold=True) - except IOError as error: - click.secho("FAIL", fg='red', bold=True) - click.echo("Error: Can't write data flash file: " + str(error)) - sys.exit(1) @main.command() @@ -216,8 +208,6 @@ def convert(input, output): infile = evic.BinFile(input.read()) outfile = evic.BinFile(infile.convert()) - try: + with handle_exceptions(IOError): + click.echo("Writing APROM image...") output.write(outfile.data) - except IOError: - click.echo("Error: Can't write converted file.") - sys.exit(1) From 70cf4b514f22988873faa396782176c073167e82 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Wed, 16 Dec 2015 14:26:32 +0200 Subject: [PATCH 06/69] Make order of options more logical --- evic/cli.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evic/cli.py b/evic/cli.py index 6240d4a..de97949 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -129,12 +129,12 @@ def verify_dataflash(data_flash): @main.command() @click.argument('input', type=click.File('rb')) -@click.option('--unencrypted/--encrypted', '-u/-e', default=False, - help='Use unencrypted/encrypted image. Defaults to encrypted.') +@click.option('--encrypted/--unencrypted', '-e/-u', default=True, + help='Use encrypted/unencrypted image. Defaults to encrypted.') @click.option('--dataflash', '-d', type=click.File('rb'), help='Use data flash from a file.') @pass_context -def upload(ctx, input, unencrypted, dataflash): +def upload(ctx, input, encrypted, dataflash): """Upload an APROM image to the device.""" find_dev(ctx.dev) @@ -142,7 +142,7 @@ def upload(ctx, input, unencrypted, dataflash): verify_dataflash(ctx.dev.data_flash) binfile = evic.BinFile(input.read()) - if unencrypted: + if not encrypted: aprom = binfile else: aprom = evic.BinFile(binfile.convert()) From 90629b78c4df1e4b0569094b805ba5a2ae83b176 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Wed, 16 Dec 2015 21:42:04 +0200 Subject: [PATCH 07/69] Don't print a newline --- evic/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evic/cli.py b/evic/cli.py index de97949..d72ddc5 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -209,5 +209,5 @@ def convert(input, output): outfile = evic.BinFile(infile.convert()) with handle_exceptions(IOError): - click.echo("Writing APROM image...") + click.echo("Writing APROM image...", nl=False) output.write(outfile.data) From b443f2de873a16327114018678933d0499c5130c Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Thu, 17 Dec 2015 00:15:35 +0200 Subject: [PATCH 08/69] Add example udev rules --- README.rst | 5 +++++ udev/99-nuvoton-hid.rules | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 udev/99-nuvoton-hid.rules diff --git a/README.rst b/README.rst index 75465d8..5a5aa05 100644 --- a/README.rst +++ b/README.rst @@ -33,6 +33,11 @@ Install from source: $ cd python-evic $ python setup.py install +Allowing non-root access to the device +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The file ``udev/99-nuvoton-hid.rules`` contains an example set of rules for setting the device permissions to ``0666``. Copy the file to the directory ``/etc/udev/rules.d/`` to use it. + Usage ------- diff --git a/udev/99-nuvoton-hid.rules b/udev/99-nuvoton-hid.rules new file mode 100644 index 0000000..0ee1b6f --- /dev/null +++ b/udev/99-nuvoton-hid.rules @@ -0,0 +1,5 @@ +# HIDAPI/libusb +SUBSYSTEM=="usb", ATTRS{idVendor}=="0416", ATTRS{idProduct}=="5020", MODE="0666" + +# HIDAPI/hidraw +KERNEL=="hidraw*", ATTRS{busnum}=="1", ATTRS{idVendor}=="0416", ATTRS{idProduct}=="5020", MODE="0666" From 67e1a0d903bba4f0891392f2e8d6c01b1be27984 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Thu, 17 Dec 2015 03:10:08 +0200 Subject: [PATCH 09/69] Add options for disabling verification --- README.rst | 11 ++++++++++- evic/cli.py | 25 +++++++++++++++++-------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 5a5aa05..9e80b27 100644 --- a/README.rst +++ b/README.rst @@ -9,7 +9,6 @@ Supported devices * Evic VTC Mini - Tested firmware versions ----------------------------- @@ -40,7 +39,10 @@ The file ``udev/99-nuvoton-hid.rules`` contains an example set of rules for sett Usage ------- +See ``--help`` for more information on a given command. +| + Encrypt/decrypt a firmware image: :: @@ -70,3 +72,10 @@ Upload a firmware image using data flash from a file: :: $ evic upload -d data.bin firmware.bin + +Use ``--no-verify`` to disable verification for APROM or data flash. To disable both: + +:: + + $ evic upload --no-verify aprom --no-verify dataflash firmware.bin + diff --git a/evic/cli.py b/evic/cli.py index d72ddc5..39f8293 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -133,13 +133,17 @@ def verify_dataflash(data_flash): help='Use encrypted/unencrypted image. Defaults to encrypted.') @click.option('--dataflash', '-d', type=click.File('rb'), help='Use data flash from a file.') +@click.option('--no-verify', 'noverify', + type=click.Choice(['aprom', 'dataflash']), multiple=True, + help='Disable verification for APROM or data flash.') @pass_context -def upload(ctx, input, encrypted, dataflash): +def upload(ctx, input, encrypted, dataflash, noverify): """Upload an APROM image to the device.""" find_dev(ctx.dev) read_data_flash(ctx.dev) - verify_dataflash(ctx.dev.data_flash) + if 'dataflash' not in noverify: + verify_dataflash(ctx.dev.data_flash) binfile = evic.BinFile(input.read()) if not encrypted: @@ -147,13 +151,15 @@ def upload(ctx, input, encrypted, dataflash): else: aprom = evic.BinFile(binfile.convert()) - with handle_exceptions(evic.FirmwareError): - click.echo("Verifying APROM...", nl=False) - aprom.verify(ctx.dev.supported_device_names) + if 'aprom' not in noverify: + with handle_exceptions(evic.FirmwareError): + click.echo("Verifying APROM...", nl=False) + aprom.verify(ctx.dev.supported_device_names) if dataflash: data_flash_file = evic.DataFlash(dataflash.read()) - verify_dataflash(data_flash_file) + if 'dataflash' not in noverify: + verify_dataflash(data_flash_file) data_flash = data_flash_file else: data_flash = ctx.dev.data_flash @@ -186,13 +192,16 @@ def upload(ctx, input, encrypted, dataflash): @main.command('dump-dataflash') @click.option('--output', '-o', type=click.File('wb')) +@click.option('--no-verify', 'noverify', is_flag=True, + help='Disable verification.') @pass_context -def dumpdataflash(ctx, output): +def dumpdataflash(ctx, output, noverify): """Write device data flash to a file.""" find_dev(ctx.dev) read_data_flash(ctx.dev) - verify_dataflash(ctx.dev.data_flash) + if not noverify: + verify_dataflash(ctx.dev.data_flash) with handle_exceptions(IOError): click.echo("Writing data flash to the file...", nl=False) From 1b7b5129b2762bea9a4dc4ec05ac67eac8d1febd Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Wed, 23 Dec 2015 23:04:06 +0200 Subject: [PATCH 10/69] Change hid command from class to a method --- evic/device.py | 88 ++++++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 53 deletions(-) diff --git a/evic/device.py b/evic/device.py index b3a8abe..e2054f7 100644 --- a/evic/device.py +++ b/evic/device.py @@ -27,57 +27,15 @@ DEVICE_NAMES = {b'E052': "eVic-VTC Mini", b'W007': "Presa TC75W"} -class HIDCmd(object): - """Nuvoton HID command class. - - Available HID command codes: - 0x35: Read data flash. - 0x53: Write data flash. - 0xB4: Reset device. - 0xC3: Write APROM. - - Attributes: - cmdcode: A list containing HID command code (1 byte). - length: A list containing HID command length, - not including the checksum (1 byte). - arg1: A list containing the first HID command argument (4 bytes). - arg2: A list containing the second HID command argument (4 bytes). - signature: A list containing the HID command signature (4 bytes). - checksum: A list containing the checksum of the HID command. - (4 bytes). - cmd: A list containing the full command (18 bytes). - """ - - signature = [byte for byte in bytearray(struct.pack('=I', 0x43444948))] - # Do not count the last 4 bytes (checksum) - length = [14] - - def __init__(self, cmdcode, arg1, arg2): - self.cmdcode = [byte for byte in bytearray(struct.pack('=B', cmdcode))] - self.arg1 = [byte for byte in bytearray(struct.pack('=I', arg1))] - self.arg2 = [byte for byte in bytearray(struct.pack('=I', arg2))] - - @property - def cmd(self): - """HID Command - - Returns: - A list containing the full HID command - """ - - cmd = self.cmdcode + self.length + self.arg1 + self.arg2 + \ - self.signature - return cmd + [byte for byte in cal_checksum(cmd)] - - class VTCMini(object): """Evic VTC Mini Attributes: - vid = USB vendor ID as an integer. - pid = USB product ID as an integer. + vid: USB vendor ID as an integer. + pid: USB product ID as an integer. supported_device_names: A list of bytestrings containing the name of the product with compatible firmware + hid_signature: A list containing the HID command signature (4 bytes). device: A HIDAPI device for the VTC Mini. manufacturer: A string containing the device manufacturer. product: A string containing the product name. @@ -89,6 +47,8 @@ class VTCMini(object): vid = 0x0416 pid = 0x5020 supported_device_names = [b'E052', b'W007'] + # 0x43444948 + hid_signature = [0x48, 0x49, 0x44, 0x43] def __init__(self): self.device = hid.device() @@ -98,6 +58,28 @@ def __init__(self): self.data_flash = None self.ldrom = False + @classmethod + def hidcmd(cls, cmdcode, arg1, arg2): + """Generates a Nuvoton HID command. + + Args: + cmdcode: The hid command as a single byte. + arg1: First HID command argument. + arg2: Second HID command argument. + + Returns: + A list containing the full HID command. + """ + + # Do not count the last 4 bytes (checksum) + length = [14] + + cmdcode = [byte for byte in bytearray(struct.pack('=B', cmdcode))] + arg1 = [byte for byte in bytearray(struct.pack('=I', arg1))] + arg2 = [byte for byte in bytearray(struct.pack('=I', arg2))] + cmd = cmdcode + length + arg1 + arg2 + cls.hid_signature + return cmd + [byte for byte in cal_checksum(cmd)] + def attach(self): """Opens the USB device. @@ -121,8 +103,8 @@ def get_sys_data(self): start = 0 end = 2048 - read_df = HIDCmd(0x35, start, end) - self.write(read_df.cmd) + read_df = self.hidcmd(0x35, start, end) + self.write(read_df) self.data_flash = DataFlash(self.read(end)) @@ -194,8 +176,8 @@ def set_sys_data(self, data_flash): start = 0 end = 2048 - write_df = HIDCmd(0x53, start, end) - self.write(write_df.cmd) + write_df = self.hidcmd(0x53, start, end) + self.write(write_df) self.write(list(data_flash.data)) @@ -203,8 +185,8 @@ def reset_system(self): """Sends the HID command for resetting the system (0xB4) """ - reset = HIDCmd(0xB4, 0, 0) - self.write(reset.cmd) + reset = self.hidcmd(0xB4, 0, 0) + self.write(reset) def upload_aprom(self, aprom): """Writes APROM to the the device. @@ -216,7 +198,7 @@ def upload_aprom(self, aprom): start = 0 end = len(aprom.data) - write_aprom = HIDCmd(0xC3, start, end) - self.write(write_aprom.cmd) + write_aprom = self.hidcmd(0xC3, start, end) + self.write(write_aprom) self.write(list(aprom.data)) From 03698e60cc67d0c010787c6063d286ea8474b9e1 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Wed, 23 Dec 2015 23:06:19 +0200 Subject: [PATCH 11/69] 3.0 is also tested now --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 9e80b27..6e022f6 100644 --- a/README.rst +++ b/README.rst @@ -16,6 +16,7 @@ Tested firmware versions * Evic VTC Mini 1.20 * Evic VTC Mini 1.30 * Evic VTC Mini 2.00 +* Evic VTC Mini 3.00 * Presa TC75W 1.02\* \*Flashing Presa firmware to a VTC Mini requires changing the hardware version From cc462c43e905ab8e4b7b1bc66f1ad8a4883270d8 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Wed, 6 Jan 2016 03:44:44 +0200 Subject: [PATCH 12/69] Binfile: Add hardware version verification --- evic/binfile.py | 24 ++++++++++++++++++++---- evic/cli.py | 3 ++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/evic/binfile.py b/evic/binfile.py index 563e283..bee2671 100644 --- a/evic/binfile.py +++ b/evic/binfile.py @@ -58,11 +58,12 @@ def convert(self): self._genfun(len(self.data), i)) & 0xFF return data - def verify(self, product_names): + def verify(self, product_names, hw_version): """Verifies the data unencrypted firmware. Args: product_names: A list of supported product names for the device. + hw_version: An integer device hardware version. Raises: FirmwareException: Verification failed. @@ -71,7 +72,22 @@ def verify(self, product_names): if b'Joyetech APROM' not in self.data: raise FirmwareError( "Firmware manufacturer verification failed.") - if not any(product_name in self.data for product_name in - product_names): - raise FirmwareError("Firmware device name verification failed.") + id_ind = 0 + max_hw_version = 0 + for product_name in product_names: + try: + id_ind = self.data.index(product_name) + max_hw_ind = id_ind + len(product_name) + max_hw_version = (self.data[max_hw_ind] * 100) + \ + (self.data[max_hw_ind + 1] * 10) + \ + (self.data[max_hw_ind + 2]) + break + except ValueError: + continue + if id_ind: + if max_hw_version < hw_version: + raise FirmwareError( + "Firmware hardware version verification failed.") + else: + raise FirmwareError("Firmware device name verification failed.") diff --git a/evic/cli.py b/evic/cli.py index 39f8293..8a84c34 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -154,7 +154,8 @@ def upload(ctx, input, encrypted, dataflash, noverify): if 'aprom' not in noverify: with handle_exceptions(evic.FirmwareError): click.echo("Verifying APROM...", nl=False) - aprom.verify(ctx.dev.supported_device_names) + aprom.verify(ctx.dev.supported_device_names, + ctx.dev.data_flash.hw_version) if dataflash: data_flash_file = evic.DataFlash(dataflash.read()) From 2b4a48600490733b53a24c77f53eb2c11b3ad6b4 Mon Sep 17 00:00:00 2001 From: ReservedField Date: Tue, 12 Jan 2016 16:36:24 +0100 Subject: [PATCH 13/69] Dataflash: Fix bootflag documentation --- evic/dataflash.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evic/dataflash.py b/evic/dataflash.py index d97328d..1f504d8 100644 --- a/evic/dataflash.py +++ b/evic/dataflash.py @@ -37,9 +37,9 @@ class DataFlash(object): hw_version: An integer hardware version. fw_version: An integer firmware version. bootflag: 0 or 1. Controls whether APROM or LDROM is booted - when the device is restarted. TODO: Confirm this. - 0 = LDROM - 1 = APROM + when the device is restarted. + 0 = APROM + 1 = LDROM checksum: A bytearray containing checksum for the data flash. """ From 2696daeca5f79ea685e760d840c63d502432877e Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Thu, 14 Jan 2016 02:19:17 +0200 Subject: [PATCH 14/69] Latest hidapi is broken --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bcffffa..6507adc 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ readme = readme_file.read() requirements = [ - 'hidapi>=0.7.99', + 'hidapi==0.7.99.post9', 'click' ] From 86ba4e3d5dfac684e678cf464bc0e7a99439d66d Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Thu, 14 Jan 2016 04:13:46 +0200 Subject: [PATCH 15/69] Add a note about hidapi requirements --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index 6e022f6..1743185 100644 --- a/README.rst +++ b/README.rst @@ -26,13 +26,20 @@ Installation ------------- Install from source: +^^^^^^^^^^^^^^^^^^^^^^ +Building hidapi requires libusb headers and cython. On Arch Linux they can be optained from the repositories by installing packages ``libusb`` and ``cython``. Debian based distributions will have packages ``libusb-1.0-0-dev`` and ``cython``. + +| + +Building python-evic: :: $ git clone git://github.com/Ban3/python-evic.git $ cd python-evic $ python setup.py install + Allowing non-root access to the device ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 950fc371710cf11bf30f19704c136a875f730218 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Thu, 14 Jan 2016 17:08:11 +0200 Subject: [PATCH 16/69] Revert "Latest hidapi is broken" Closes #7 This reverts commit 2696daeca5f79ea685e760d840c63d502432877e. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6507adc..bcffffa 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ readme = readme_file.read() requirements = [ - 'hidapi==0.7.99.post9', + 'hidapi>=0.7.99', 'click' ] From fda94258217549f7e9cf94ca03eb82f17444410e Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Thu, 14 Jan 2016 17:18:01 +0200 Subject: [PATCH 17/69] Fix typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1743185..88d28ce 100644 --- a/README.rst +++ b/README.rst @@ -28,7 +28,7 @@ Installation Install from source: ^^^^^^^^^^^^^^^^^^^^^^ -Building hidapi requires libusb headers and cython. On Arch Linux they can be optained from the repositories by installing packages ``libusb`` and ``cython``. Debian based distributions will have packages ``libusb-1.0-0-dev`` and ``cython``. +Building hidapi requires libusb headers and cython. On Arch Linux they can be obtained from the repositories by installing packages ``libusb`` and ``cython``. Debian based distributions will have packages ``libusb-1.0-0-dev`` and ``cython``. | From d345ad1cd74b45413c307b4e01cfa04a5c1b897e Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Thu, 14 Jan 2016 17:26:05 +0200 Subject: [PATCH 18/69] Add notes about Windows --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 88d28ce..58340f2 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,9 @@ Install from source: Building hidapi requires libusb headers and cython. On Arch Linux they can be obtained from the repositories by installing packages ``libusb`` and ``cython``. Debian based distributions will have packages ``libusb-1.0-0-dev`` and ``cython``. +On Windows you will also need the correct compiler for your Python version. See `this `_ +page for more information on setting up the compiler. + | Building python-evic: From db58af5fd2eb5d780524578d4778e679633ac0da Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Fri, 15 Jan 2016 16:09:08 +0200 Subject: [PATCH 19/69] Support Wismec devices --- README.rst | 7 +++++++ evic/__init__.py | 2 +- evic/cli.py | 14 +++++++------- evic/device.py | 25 ++++++++++++++++++------- 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/README.rst b/README.rst index 58340f2..bed3339 100644 --- a/README.rst +++ b/README.rst @@ -8,6 +8,13 @@ Supported devices --------------------- * Evic VTC Mini +* Presa TC75W* +* Vaporflask Classic* +* Vaporflask Lite* +* Vaporflask Stout* +* Reuleaux RX200* + +\*Untested Tested firmware versions ----------------------------- diff --git a/evic/__init__.py b/evic/__init__.py index 44da879..16cdd9c 100644 --- a/evic/__init__.py +++ b/evic/__init__.py @@ -20,7 +20,7 @@ __version__ = '0.1.dev0' -from .device import DEVICE_NAMES, VTCMini +from .device import HIDTransfer from .helpers import cal_checksum from .binfile import BinFile, FirmwareError from .dataflash import DataFlash, DataFlashError diff --git a/evic/cli.py b/evic/cli.py index 8a84c34..3ecdb07 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -32,12 +32,11 @@ class Context(object): """Click context. Attributes: - device_names: A dictionary mapping of device names. - dev: An instance of evic.VTCMini. + dev: An instance of evic.HIDTransfer. """ def __init__(self): - self.dev = evic.VTCMini() + self.dev = evic.HIDTransfer() pass_context = click.make_pass_decorator(Context, ensure=True) @@ -98,8 +97,8 @@ def read_data_flash(dev): or not dev.data_flash.fw_version: dev.get_sys_data() - if dev.data_flash.device_name in evic.DEVICE_NAMES: - devicename = evic.DEVICE_NAMES[dev.data_flash.device_name] + if dev.data_flash.device_name in dev.device_names: + devicename = dev.device_names[dev.data_flash.device_name] else: devicename = "Unknown device" @@ -154,8 +153,9 @@ def upload(ctx, input, encrypted, dataflash, noverify): if 'aprom' not in noverify: with handle_exceptions(evic.FirmwareError): click.echo("Verifying APROM...", nl=False) - aprom.verify(ctx.dev.supported_device_names, - ctx.dev.data_flash.hw_version) + aprom.verify( + ctx.dev.supported_device_names[ctx.dev.data_flash.device_name], + ctx.dev.data_flash.hw_version) if dataflash: data_flash_file = evic.DataFlash(dataflash.read()) diff --git a/evic/device.py b/evic/device.py index e2054f7..bd72a4b 100644 --- a/evic/device.py +++ b/evic/device.py @@ -24,17 +24,17 @@ from .dataflash import DataFlash from .helpers import cal_checksum -DEVICE_NAMES = {b'E052': "eVic-VTC Mini", b'W007': "Presa TC75W"} - -class VTCMini(object): - """Evic VTC Mini +class HIDTransfer(object): + """Generic Nuvoton HID Transfer device Attributes: vid: USB vendor ID as an integer. pid: USB product ID as an integer. - supported_device_names: A list of bytestrings containing the name of - the product with compatible firmware + device_names: A dictionary mapping product IDs to device name bytestrings. + supported_device_names: A dictionary mapping device name to a list of + bytestrings containing the names of + the products with compatible firmware hid_signature: A list containing the HID command signature (4 bytes). device: A HIDAPI device for the VTC Mini. manufacturer: A string containing the device manufacturer. @@ -46,7 +46,18 @@ class VTCMini(object): vid = 0x0416 pid = 0x5020 - supported_device_names = [b'E052', b'W007'] + device_names = {b'E052': "eVic-VTC Mini", + b'W007': "Presa TC75W", + b'W010': "Classic", + b'W011': "Lite", + b'W013': "Stout", + b'W014': "Reuleaux RX200"} + supported_device_names = {b'E052': [b'E052', b'W007'], + b'W007': [b'W007'], + b'W010': [b'W010'], + b'W011': [b'W011'], + b'W013': [b'W013'], + b'W014': [b'W014']} # 0x43444948 hid_signature = [0x48, 0x49, 0x44, 0x43] From c7a4fd26b428e9281881599e4a3cf509ac8f4689 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Fri, 15 Jan 2016 16:11:19 +0200 Subject: [PATCH 20/69] Combine tested firmware versions --- README.rst | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.rst b/README.rst index bed3339..1e49f26 100644 --- a/README.rst +++ b/README.rst @@ -19,11 +19,7 @@ Supported devices Tested firmware versions ----------------------------- -* Evic VTC Mini 1.10 -* Evic VTC Mini 1.20 -* Evic VTC Mini 1.30 -* Evic VTC Mini 2.00 -* Evic VTC Mini 3.00 +* Evic VTC Mini <=3.0 * Presa TC75W 1.02\* \*Flashing Presa firmware to a VTC Mini requires changing the hardware version From a14faf153f96b098af64fafcaaa0f2f3a69adcc9 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Fri, 15 Jan 2016 16:40:05 +0200 Subject: [PATCH 21/69] Link to evic-sdk --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 1e49f26..36e706c 100644 --- a/README.rst +++ b/README.rst @@ -21,6 +21,7 @@ Tested firmware versions * Evic VTC Mini <=3.0 * Presa TC75W 1.02\* +* Examples from `evic-sdk `_ \*Flashing Presa firmware to a VTC Mini requires changing the hardware version on some devices. Backup your data flash before flashing! From ad18c3f1fcb7eebb3efd1c1d136d4c4d5493f38f Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Sat, 16 Jan 2016 10:46:27 +0200 Subject: [PATCH 22/69] Installation requires setuptools --- setup.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/setup.py b/setup.py index bcffffa..1a4ca1b 100644 --- a/setup.py +++ b/setup.py @@ -18,10 +18,7 @@ along """ -try: - from setuptools import setup -except ImportError: - from distutils.core import setup +from setuptools import setup with open('README.rst') as readme_file: readme = readme_file.read() From ae7f6081debeb40b176aacac57810785ddef46b1 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Sat, 16 Jan 2016 11:15:10 +0200 Subject: [PATCH 23/69] Note the setuptools requirement --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 36e706c..46fe14f 100644 --- a/README.rst +++ b/README.rst @@ -37,9 +37,12 @@ Building hidapi requires libusb headers and cython. On Arch Linux they can be ob On Windows you will also need the correct compiler for your Python version. See `this `_ page for more information on setting up the compiler. +Building python-evic requires that you have setuptools installed. + | Building python-evic: + :: $ git clone git://github.com/Ban3/python-evic.git From aaf75b65add2dbc0b2d2041593a4bc6c23ccc6c7 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Sat, 16 Jan 2016 18:18:02 +0200 Subject: [PATCH 24/69] Bootstrap setuptools if they're not available --- README.rst | 2 - ez_setup.py | 415 ++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 2 + 3 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 ez_setup.py diff --git a/README.rst b/README.rst index 46fe14f..b0e39c2 100644 --- a/README.rst +++ b/README.rst @@ -37,8 +37,6 @@ Building hidapi requires libusb headers and cython. On Arch Linux they can be ob On Windows you will also need the correct compiler for your Python version. See `this `_ page for more information on setting up the compiler. -Building python-evic requires that you have setuptools installed. - | Building python-evic: diff --git a/ez_setup.py b/ez_setup.py new file mode 100644 index 0000000..9715bdc --- /dev/null +++ b/ez_setup.py @@ -0,0 +1,415 @@ +#!/usr/bin/env python + +""" +Setuptools bootstrapping installer. + +Run this script to install or upgrade setuptools. +""" + +import os +import shutil +import sys +import tempfile +import zipfile +import optparse +import subprocess +import platform +import textwrap +import contextlib +import json +import codecs + +from distutils import log + +try: + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +LATEST = object() +DEFAULT_VERSION = LATEST +DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" +DEFAULT_SAVE_DIR = os.curdir + + +def _python_cmd(*args): + """ + Execute a command. + + Return True if the command succeeded. + """ + args = (sys.executable,) + args + return subprocess.call(args) == 0 + + +def _install(archive_filename, install_args=()): + """Install Setuptools.""" + with archive_context(archive_filename): + # installing + log.warn('Installing Setuptools') + if not _python_cmd('setup.py', 'install', *install_args): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') + # exitcode will be 2 + return 2 + + +def _build_egg(egg, archive_filename, to_dir): + """Build Setuptools egg.""" + with archive_context(archive_filename): + # building an egg + log.warn('Building a Setuptools egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +class ContextualZipFile(zipfile.ZipFile): + + """Supplement ZipFile class to support context manager for Python 2.6.""" + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() + + def __new__(cls, *args, **kwargs): + """Construct a ZipFile or ContextualZipFile as appropriate.""" + if hasattr(zipfile.ZipFile, '__exit__'): + return zipfile.ZipFile(*args, **kwargs) + return super(ContextualZipFile, cls).__new__(cls) + + +@contextlib.contextmanager +def archive_context(filename): + """ + Unzip filename to a temporary directory, set to the cwd. + + The unzipped target is cleaned up after. + """ + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + with ContextualZipFile(filename) as archive: + archive.extractall() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + yield + + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + + +def _do_download(version, download_base, to_dir, download_delay): + """Download Setuptools.""" + egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + archive = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, archive, to_dir) + sys.path.insert(0, egg) + + # Remove previously-imported pkg_resources if present (see + # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). + if 'pkg_resources' in sys.modules: + _unload_pkg_resources() + + import setuptools + setuptools.bootstrap_install_from = egg + + +def use_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=DEFAULT_SAVE_DIR, download_delay=15): + """ + Ensure that a setuptools version is installed. + + Return None. Raise SystemExit if the requested version + or later cannot be installed. + """ + version = _resolve_version(version) + to_dir = os.path.abspath(to_dir) + + # prior to importing, capture the module state for + # representative modules. + rep_modules = 'pkg_resources', 'setuptools' + imported = set(sys.modules).intersection(rep_modules) + + try: + import pkg_resources + pkg_resources.require("setuptools>=" + version) + # a suitable version is already installed + return + except ImportError: + # pkg_resources not available; setuptools is not installed; download + pass + except pkg_resources.DistributionNotFound: + # no version of setuptools was found; allow download + pass + except pkg_resources.VersionConflict as VC_err: + if imported: + _conflict_bail(VC_err, version) + + # otherwise, unload pkg_resources to allow the downloaded version to + # take precedence. + del pkg_resources + _unload_pkg_resources() + + return _do_download(version, download_base, to_dir, download_delay) + + +def _conflict_bail(VC_err, version): + """ + Setuptools was imported prior to invocation, so it is + unsafe to unload it. Bail out. + """ + conflict_tmpl = textwrap.dedent(""" + The required version of setuptools (>={version}) is not available, + and can't be installed while this script is running. Please + install a more recent version first, using + 'easy_install -U setuptools'. + + (Currently using {VC_err.args[0]!r}) + """) + msg = conflict_tmpl.format(**locals()) + sys.stderr.write(msg) + sys.exit(2) + + +def _unload_pkg_resources(): + del_modules = [ + name for name in sys.modules + if name.startswith('pkg_resources') + ] + for mod_name in del_modules: + del sys.modules[mod_name] + + +def _clean_check(cmd, target): + """ + Run the command to download target. + + If the command fails, clean up before re-raising the error. + """ + try: + subprocess.check_call(cmd) + except subprocess.CalledProcessError: + if os.access(target, os.F_OK): + os.unlink(target) + raise + + +def download_file_powershell(url, target): + """ + Download the file at url to target using Powershell. + + Powershell will validate trust. + Raise an exception if the command cannot complete. + """ + target = os.path.abspath(target) + ps_cmd = ( + "[System.Net.WebRequest]::DefaultWebProxy.Credentials = " + "[System.Net.CredentialCache]::DefaultCredentials; " + '(new-object System.Net.WebClient).DownloadFile("%(url)s", "%(target)s")' + % locals() + ) + cmd = [ + 'powershell', + '-Command', + ps_cmd, + ] + _clean_check(cmd, target) + + +def has_powershell(): + """Determine if Powershell is available.""" + if platform.system() != 'Windows': + return False + cmd = ['powershell', '-Command', 'echo test'] + with open(os.path.devnull, 'wb') as devnull: + try: + subprocess.check_call(cmd, stdout=devnull, stderr=devnull) + except Exception: + return False + return True +download_file_powershell.viable = has_powershell + + +def download_file_curl(url, target): + cmd = ['curl', url, '--silent', '--output', target] + _clean_check(cmd, target) + + +def has_curl(): + cmd = ['curl', '--version'] + with open(os.path.devnull, 'wb') as devnull: + try: + subprocess.check_call(cmd, stdout=devnull, stderr=devnull) + except Exception: + return False + return True +download_file_curl.viable = has_curl + + +def download_file_wget(url, target): + cmd = ['wget', url, '--quiet', '--output-document', target] + _clean_check(cmd, target) + + +def has_wget(): + cmd = ['wget', '--version'] + with open(os.path.devnull, 'wb') as devnull: + try: + subprocess.check_call(cmd, stdout=devnull, stderr=devnull) + except Exception: + return False + return True +download_file_wget.viable = has_wget + + +def download_file_insecure(url, target): + """Use Python to download the file, without connection authentication.""" + src = urlopen(url) + try: + # Read all the data in one block. + data = src.read() + finally: + src.close() + + # Write all the data in one block to avoid creating a partial file. + with open(target, "wb") as dst: + dst.write(data) +download_file_insecure.viable = lambda: True + + +def get_best_downloader(): + downloaders = ( + download_file_powershell, + download_file_curl, + download_file_wget, + download_file_insecure, + ) + viable_downloaders = (dl for dl in downloaders if dl.viable()) + return next(viable_downloaders, None) + + +def download_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=DEFAULT_SAVE_DIR, delay=15, + downloader_factory=get_best_downloader): + """ + Download setuptools from a specified location and return its filename. + + `version` should be a valid setuptools version number that is available + as an sdist for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download + attempt. + + ``downloader_factory`` should be a function taking no arguments and + returning a function for downloading a URL to a target. + """ + version = _resolve_version(version) + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + zip_name = "setuptools-%s.zip" % version + url = download_base + zip_name + saveto = os.path.join(to_dir, zip_name) + if not os.path.exists(saveto): # Avoid repeated downloads + log.warn("Downloading %s", url) + downloader = downloader_factory() + downloader(url, saveto) + return os.path.realpath(saveto) + + +def _resolve_version(version): + """ + Resolve LATEST version + """ + if version is not LATEST: + return version + + resp = urlopen('https://pypi.python.org/pypi/setuptools/json') + with contextlib.closing(resp): + try: + charset = resp.info().get_content_charset() + except Exception: + # Python 2 compat; assume UTF-8 + charset = 'UTF-8' + reader = codecs.getreader(charset) + doc = json.load(reader(resp)) + + return str(doc['info']['version']) + + +def _build_install_args(options): + """ + Build the arguments to 'python setup.py install' on the setuptools package. + + Returns list of command line arguments. + """ + return ['--user'] if options.user_install else [] + + +def _parse_args(): + """Parse the command line for options.""" + parser = optparse.OptionParser() + parser.add_option( + '--user', dest='user_install', action='store_true', default=False, + help='install in user site package (requires Python 2.6 or later)') + parser.add_option( + '--download-base', dest='download_base', metavar="URL", + default=DEFAULT_URL, + help='alternative URL from where to download the setuptools package') + parser.add_option( + '--insecure', dest='downloader_factory', action='store_const', + const=lambda: download_file_insecure, default=get_best_downloader, + help='Use internal, non-validating downloader' + ) + parser.add_option( + '--version', help="Specify which version to download", + default=DEFAULT_VERSION, + ) + parser.add_option( + '--to-dir', + help="Directory to save (and re-use) package", + default=DEFAULT_SAVE_DIR, + ) + options, args = parser.parse_args() + # positional arguments are ignored + return options + + +def _download_args(options): + """Return args for download_setuptools function from cmdline args.""" + return dict( + version=options.version, + download_base=options.download_base, + downloader_factory=options.downloader_factory, + to_dir=options.to_dir, + ) + + +def main(): + """Install or upgrade setuptools and EasyInstall.""" + options = _parse_args() + archive = download_setuptools(**_download_args(options)) + return _install(archive, _build_install_args(options)) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/setup.py b/setup.py index 1a4ca1b..1492bd4 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,8 @@ You should have received a copy of the GNU General Public License along """ +import ez_setup +ez_setup.use_setuptools() from setuptools import setup From 35e52ca59691e0a94b5e0b5ac60f0fc6c0d935a9 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Thu, 21 Jan 2016 20:14:56 +0200 Subject: [PATCH 25/69] Support Joyetech Cuboid --- README.rst | 1 + evic/device.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/README.rst b/README.rst index b0e39c2..78aec45 100644 --- a/README.rst +++ b/README.rst @@ -8,6 +8,7 @@ Supported devices --------------------- * Evic VTC Mini +* Cuboid* * Presa TC75W* * Vaporflask Classic* * Vaporflask Lite* diff --git a/evic/device.py b/evic/device.py index bd72a4b..1c50565 100644 --- a/evic/device.py +++ b/evic/device.py @@ -47,12 +47,14 @@ class HIDTransfer(object): vid = 0x0416 pid = 0x5020 device_names = {b'E052': "eVic-VTC Mini", + b'E060': "Cuboid", b'W007': "Presa TC75W", b'W010': "Classic", b'W011': "Lite", b'W013': "Stout", b'W014': "Reuleaux RX200"} supported_device_names = {b'E052': [b'E052', b'W007'], + b'E060': [b'E060'], b'W007': [b'W007'], b'W010': [b'W010'], b'W011': [b'W011'], From 55ed700699e8b4550409c2e553e098263788320d Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 25 Jan 2016 02:53:09 +0200 Subject: [PATCH 26/69] Cleanup --- evic/__init__.py | 2 +- evic/{binfile.py => aprom.py} | 49 +++++--- evic/cli.py | 211 ++++++++++++++++++++-------------- evic/dataflash.py | 100 ++++------------ evic/device.py | 142 +++++++++++++---------- evic/helpers.py | 33 ------ setup.py | 8 +- 7 files changed, 268 insertions(+), 277 deletions(-) rename evic/{binfile.py => aprom.py} (61%) delete mode 100644 evic/helpers.py diff --git a/evic/__init__.py b/evic/__init__.py index 16cdd9c..4c1a964 100644 --- a/evic/__init__.py +++ b/evic/__init__.py @@ -22,5 +22,5 @@ from .device import HIDTransfer from .helpers import cal_checksum -from .binfile import BinFile, FirmwareError +from .aprom import APROM, APROMError from .dataflash import DataFlash, DataFlashError diff --git a/evic/binfile.py b/evic/aprom.py similarity index 61% rename from evic/binfile.py rename to evic/aprom.py index bee2671..02c45b6 100644 --- a/evic/binfile.py +++ b/evic/aprom.py @@ -17,18 +17,20 @@ along with this program. If not, see . """ +import struct -class FirmwareError(Exception): - """Firmware verification error.""" + +class APROMError(Exception): + """APROM verification error.""" pass -class BinFile(object): - """Firmware binary file class +class APROM(object): + """APROM file class Attributes: - data: A bytearray containing binary data of the firmware. + data: A bytearray containing the binary data of the firmware. """ def __init__(self, data): @@ -39,8 +41,8 @@ def _genfun(filesize, index): """Generator function for decrypting/encrypting the binary file. Args: - filesize: An integer, filesize of the binary file. - index: An integer, index of the byte that is being decrypted. + filesize: Filesize of the APROM file in bytes. + index: Index of the byte being converted. """ return filesize + 408376 + index - filesize // 408376 @@ -59,7 +61,9 @@ def convert(self): return data def verify(self, product_names, hw_version): - """Verifies the data unencrypted firmware. + """Verifies the contained data. + + Data needs to be unencrypted. Args: product_names: A list of supported product names for the device. @@ -69,25 +73,32 @@ def verify(self, product_names, hw_version): FirmwareException: Verification failed. """ + + # Does the APROM contain the string "Joyetech APROM"? if b'Joyetech APROM' not in self.data: - raise FirmwareError( - "Firmware manufacturer verification failed.") + raise APROMError("Firmware manufacturer verification failed.") id_ind = 0 max_hw_version = 0 + # Try to locate supported product IDs for product_name in product_names: try: + product_name = product_name.encode() id_ind = self.data.index(product_name) + # Maximum hardware version follows the product ID max_hw_ind = id_ind + len(product_name) - max_hw_version = (self.data[max_hw_ind] * 100) + \ - (self.data[max_hw_ind + 1] * 10) + \ - (self.data[max_hw_ind + 2]) + max_hw_version = struct.unpack("=I", b'\x00' + + self.data[max_hw_ind:max_hw_ind+3])[0] break + # Product ID was not found, try the next one except ValueError: continue - if id_ind: - if max_hw_version < hw_version: - raise FirmwareError( - "Firmware hardware version verification failed.") - else: - raise FirmwareError("Firmware device name verification failed.") + + # Raise an error if none of the supported product IDs were found + if not id_ind: + raise APROMError("Firmware device name verification failed.") + + # Raise an error if the maximum supported hardware version is less than + # the supplied hardware version + if max_hw_version < hw_version: + raise APROMError("Firmware hardware version verification failed.") diff --git a/evic/cli.py b/evic/cli.py index 3ecdb07..7c68815 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -28,19 +28,6 @@ import evic -class Context(object): - """Click context. - - Attributes: - dev: An instance of evic.HIDTransfer. - """ - - def __init__(self): - self.dev = evic.HIDTransfer() - -pass_context = click.make_pass_decorator(Context, ensure=True) - - @contextmanager def handle_exceptions(*exceptions): """Context for handling exceptions.""" @@ -61,18 +48,30 @@ def main(): pass -def find_dev(dev): - """Attaches the USB device. - - Attaches the device and prints the device information to the screen. +def connect(dev): + """Connects the USB device. Args: - dev: An instance of the device. + dev: evic.HIDTransfer object. + + Raises: + IOerror: The device was not found """ + # Connect the device with handle_exceptions(IOError): click.echo("\nFinding device...", nl=False) - dev.attach() + dev.connect() + if not dev.manufacturer: + raise IOError("Device not found.") + + +def print_usb_info(dev): + """Prints the USB information attributes of the device + + Args: + dev: evic.HIDTransfer object + """ click.echo("\tManufacturer: ", nl=False) click.secho(dev.manufacturer, bold=True) @@ -83,141 +82,185 @@ def find_dev(dev): click.echo("") -def read_data_flash(dev): - """Reads the data flash from the device. +def read_dataflash(dev, verify): + """Reads the device data flash. Args: - dev: An instance of the attached device. + dev: evic.HIDTransfer object. + verify: A Boolean set to True to verify the data flash. + + Returns: + evic.DataFlash object containing the device data flash. """ + # Read the data flash with handle_exceptions(IOError): click.echo("Reading data flash...", nl=False) - dev.get_sys_data() - if struct.unpack("=I", dev.data_flash.data[264:268])[0] \ - or not dev.data_flash.fw_version: - dev.get_sys_data() + dataflash, checksum = dev.get_sys_data() + + # Verify the data flash + if verify: + verify_dataflash(dataflash, checksum) + + return dataflash - if dev.data_flash.device_name in dev.device_names: - devicename = dev.device_names[dev.data_flash.device_name] - else: - devicename = "Unknown device" +def print_device_info(dev, dataflash): + """Prints the device information found from data flash. + + Args: + dev: evic.HIDTransfer object. + dataflash: evic.DataFlash object. + """ + + # Find the device name + devicename = dev.device_names.get(dataflash.device_name, "Unknown device") + + # Print out the information click.echo("\tDevice name: ", nl=False) click.secho(devicename, bold=True) click.echo("\tFirmware version: ", nl=False) - click.secho("{0:.2f}".format(dev.data_flash.fw_version / 100.0), bold=True) + click.secho("{0:.2f}".format(dataflash.fw_version / 100.0), bold=True) click.echo("\tHardware version: ", nl=False) - click.secho("{0:.2f}\n".format( - dev.data_flash.hw_version / 100.0), bold=True) + click.secho("{0:.2f}\n".format(dataflash.hw_version / 100.0), bold=True) - if dev.data_flash.hw_version > 1000: + # Issue a warning about unset hardware version number + if dataflash.hw_version > 1000: click.echo("Please set the hardware version.") -def verify_dataflash(data_flash): +def verify_dataflash(dataflash, checksum): """Verifies that the data flash is correct. Args: - data_flash: An instance of evic.DataFlash. + data_flash: evic.DataFlash object. + checksum: Checksum used for the verification. """ with handle_exceptions(evic.DataFlashError): click.echo("Verifying data flash...", nl=False) - data_flash.verify() + dataflash.verify(checksum) @main.command() -@click.argument('input', type=click.File('rb')) +@click.argument('inputfile', type=click.File('rb')) @click.option('--encrypted/--unencrypted', '-e/-u', default=True, help='Use encrypted/unencrypted image. Defaults to encrypted.') -@click.option('--dataflash', '-d', type=click.File('rb'), +@click.option('--dataflash', 'dataflashfile', '-d', type=click.File('rb'), help='Use data flash from a file.') @click.option('--no-verify', 'noverify', type=click.Choice(['aprom', 'dataflash']), multiple=True, help='Disable verification for APROM or data flash.') -@pass_context -def upload(ctx, input, encrypted, dataflash, noverify): +def upload(inputfile, encrypted, dataflashfile, noverify): """Upload an APROM image to the device.""" - find_dev(ctx.dev) - read_data_flash(ctx.dev) - if 'dataflash' not in noverify: - verify_dataflash(ctx.dev.data_flash) + dev = evic.HIDTransfer() + + # Connect the device + connect(dev) + + # Print the USB info of the device + print_usb_info(dev) + + # Read the data flash + verify = 'dataflash' not in noverify + dataflash = read_dataflash(dev, verify) - binfile = evic.BinFile(input.read()) - if not encrypted: - aprom = binfile - else: - aprom = evic.BinFile(binfile.convert()) + # Print the device information + print_device_info(dev, dataflash) + # Read the APROM image + aprom = evic.APROM(inputfile.read()) + if encrypted: + aprom = evic.APROM(aprom.convert()) + + # Verify the APROM image if 'aprom' not in noverify: - with handle_exceptions(evic.FirmwareError): + with handle_exceptions(evic.APROMError): click.echo("Verifying APROM...", nl=False) aprom.verify( - ctx.dev.supported_device_names[ctx.dev.data_flash.device_name], - ctx.dev.data_flash.hw_version) - - if dataflash: - data_flash_file = evic.DataFlash(dataflash.read()) + dev.supported_device_names[dataflash.device_name], + dataflash.hw_version) + + # Are we using a data flash file? + if dataflashfile: + buf = list(dataflashfile.read()) + # We used to store the checksum inside the file + if len(buf) == 2048: + checksum = struct.unpack("=I", bytearray(buf[0:4]))[0] + dataflash = evic.DataFlash(buf[4:], 0) + else: + checksum = sum(buf) + dataflash = evic.DataFlash(buf, 0) if 'dataflash' not in noverify: - verify_dataflash(data_flash_file) - data_flash = data_flash_file - else: - data_flash = ctx.dev.data_flash + verify_dataflash(dataflash, checksum) - data_flash.bootflag = 1 + # We want to boot to LDROM on restart + dataflash.bootflag = 1 # Flashing Presa firmware requires HW version <=1.03 on type A devices - if b'W007' in aprom.data and data_flash.device_name == b'E052' \ - and data_flash.hw_version in [106, 108, 109, 111]: + if b'W007' in aprom.data and dataflash.device_name == 'E052' \ + and dataflash.hw_version in [106, 108, 109, 111]: click.echo("Changing HW version to 1.03...", nl=False) - data_flash.hw_version = 103 + dataflash.hw_version = 103 click.secho("OK", fg='green', bold=True) + # Write data flash to the device with handle_exceptions(IOError): click.echo("Writing data flash...", nl=False) - ctx.dev.set_sys_data(data_flash) + dev.set_sys_data(dataflash) click.secho("OK", fg='green', bold=True) - if not ctx.dev.ldrom: + + # We should only restart if we're not in LDROM + if not dev.ldrom: + # Restart click.echo("Restarting the device...", nl=False) - ctx.dev.reset_system() + dev.reset_system() sleep(2) - click.secho("OK", fg='green', bold=True) - click.echo("Reconnecting the device...", nl=False) - ctx.dev.attach() - click.secho("OK", fg='green', bold=True) + click.secho("OK", fg='green', nl=False, bold=True) + # Reconnect + connect(dev) + # Write APROM to the device click.echo("Writing APROM...", nl=False) - ctx.dev.upload_aprom(aprom) + dev.upload_aprom(aprom) @main.command('dump-dataflash') @click.option('--output', '-o', type=click.File('wb')) @click.option('--no-verify', 'noverify', is_flag=True, help='Disable verification.') -@pass_context -def dumpdataflash(ctx, output, noverify): +def dumpdataflash(output, noverify): """Write device data flash to a file.""" - find_dev(ctx.dev) - read_data_flash(ctx.dev) - if not noverify: - verify_dataflash(ctx.dev.data_flash) + dev = evic.HIDTransfer() + + # Connect the device + connect(dev) + + # Print the USB info of the device + print_usb_info(dev) + + # Read the data flash + dataflash = read_dataflash(dev, noverify) + + # Print the device information + print_device_info(dev, dataflash) + # Write the data flash to the file with handle_exceptions(IOError): click.echo("Writing data flash to the file...", nl=False) - output.write(ctx.dev.data_flash.data) + output.write(bytearray(dataflash.array)) @main.command() -@click.argument('input', type=click.File('rb')) +@click.argument('inputfile', type=click.File('rb')) @click.option('--output', '-o', type=click.File('wb')) -def convert(input, output): +def convert(inputfile, output): """Decrypt/encrypt an APROM image.""" - infile = evic.BinFile(input.read()) - outfile = evic.BinFile(infile.convert()) + binfile = evic.APROM(inputfile.read()) with handle_exceptions(IOError): click.echo("Writing APROM image...", nl=False) - output.write(outfile.data) + output.write(binfile.convert()) diff --git a/evic/dataflash.py b/evic/dataflash.py index 1f504d8..b15d577 100644 --- a/evic/dataflash.py +++ b/evic/dataflash.py @@ -17,9 +17,7 @@ along with this program. If not, see . """ -import struct - -from .helpers import cal_checksum +import binstruct class DataFlashError(Exception): @@ -28,91 +26,37 @@ class DataFlashError(Exception): pass -class DataFlash(object): +class DataFlash(binstruct.StructTemplate): """Device data flash class. Attributes: - data: A bytearray containing binary data of the data flash. - device_name: A bytestring containing device name. - hw_version: An integer hardware version. - fw_version: An integer firmware version. + hw_version: An integer hardware version number. bootflag: 0 or 1. Controls whether APROM or LDROM is booted - when the device is restarted. - 0 = APROM - 1 = LDROM - checksum: A bytearray containing checksum for the data flash. + when the device is restarted. + 0 = APROM + 1 = LDROM + device_name: Device name string. + fw_version: An integer firmware version number. + unknown1: TODO + unknown2: TODO """ - def __init__(self, data): - self.data = bytearray(data) - self._device_name = bytes(self.data[316:320]) - self._hw_version = struct.unpack("=I", self.data[8:12])[0] - self._fw_version = struct.unpack("=I", self.data[260:264])[0] - self._bootflag = self.data[13] - self._checksum = self.data[0:4] - - @property - def device_name(self): - return self._device_name - - @device_name.setter - def device_name(self, device_name): - self.device_name = device_name - self.data[316:320] = bytearray(struct.pack("4s", device_name)) - self.update_checksum() - - @property - def hw_version(self): - return self._hw_version - - @hw_version.setter - def hw_version(self, version): - self._hw_version = version - self.data[8:12] = bytearray(struct.pack("=I", version)) - self.update_checksum() - - @property - def fw_version(self): - return self._fw_version - - @fw_version.setter - def fw_version(self, version): - self._fw_version = version - self.data[260:264] = bytearray(struct.pack("=I", version)) - self.update_checksum() - - @property - def bootflag(self): - return self._bootflag - - @bootflag.setter - def bootflag(self, flag): - self._bootflag = flag - self.data[13] = flag - self.update_checksum() - - @property - def checksum(self): - return self._checksum - - @checksum.setter - def checksum(self, checksum): - self._checksum = checksum - self.data[0:4] = checksum - - def update_checksum(self): - """Updates the checksum for the data flash data.""" + hw_version = binstruct.Int32Field(4) + bootflag = binstruct.Int8Field(9) + device_name = binstruct.StringField(312, 4) + fw_version = binstruct.Int32Field(256) + unknown1 = binstruct.Int32Field(260) + unknown2 = binstruct.Int32Field(264) - self.checksum = cal_checksum(self.data[4:]) + def verify(self, checksum): + """Verifies the data flash against given checksum. - def verify(self): - """Verifies the data flash. + Args: + checksum: Checksum of the data. Raises: DataFlashError: Data flash verification failed. """ - if cal_checksum(self.data[4:]) != self.checksum \ - or not struct.unpack("=I", self.checksum)[0] \ - | struct.unpack("=I", self.data[268:272])[0]: - raise DataFlashError("Data flash verification failed") + if sum(self.array) != checksum or not checksum | self.unknown2: + raise DataFlashError("Data flash verification failed.") diff --git a/evic/device.py b/evic/device.py index 1c50565..9113bc4 100644 --- a/evic/device.py +++ b/evic/device.py @@ -22,44 +22,43 @@ import hid from .dataflash import DataFlash -from .helpers import cal_checksum class HIDTransfer(object): - """Generic Nuvoton HID Transfer device + """Generic Nuvoton HID Transfer device class. Attributes: - vid: USB vendor ID as an integer. - pid: USB product ID as an integer. - device_names: A dictionary mapping product IDs to device name bytestrings. + vid: USB vendor ID. + pid: USB product ID. + device_names: A dictionary mapping product IDs to + device name strings. supported_device_names: A dictionary mapping device name to a list of - bytestrings containing the names of + strings containing the names of the products with compatible firmware hid_signature: A list containing the HID command signature (4 bytes). - device: A HIDAPI device for the VTC Mini. + device: A HIDAPI device. manufacturer: A string containing the device manufacturer. product: A string containing the product name. serial: A string conraining the product serial number. - data_flash: An instance of DataFlash containing the device data flash. ldrom: A Boolean value set to True if the device is booted to LDROM. """ vid = 0x0416 pid = 0x5020 - device_names = {b'E052': "eVic-VTC Mini", - b'E060': "Cuboid", - b'W007': "Presa TC75W", - b'W010': "Classic", - b'W011': "Lite", - b'W013': "Stout", - b'W014': "Reuleaux RX200"} - supported_device_names = {b'E052': [b'E052', b'W007'], - b'E060': [b'E060'], - b'W007': [b'W007'], - b'W010': [b'W010'], - b'W011': [b'W011'], - b'W013': [b'W013'], - b'W014': [b'W014']} + device_names = {'E052': "eVic-VTC Mini", + 'E060': "Cuboid", + 'W007': "Presa TC75W", + 'W010': "Classic", + 'W011': "Lite", + 'W013': "Stout", + 'W014': "Reuleaux RX200"} + supported_device_names = {'E052': ['E052', 'W007'], + 'E060': ['E060'], + 'W007': ['W007'], + 'W010': ['W010'], + 'W011': ['W011'], + 'W013': ['W013'], + 'W014': ['W014']} # 0x43444948 hid_signature = [0x48, 0x49, 0x44, 0x43] @@ -68,7 +67,6 @@ def __init__(self): self.manufacturer = None self.product = None self.serial = None - self.data_flash = None self.ldrom = False @classmethod @@ -76,9 +74,9 @@ def hidcmd(cls, cmdcode, arg1, arg2): """Generates a Nuvoton HID command. Args: - cmdcode: The hid command as a single byte. - arg1: First HID command argument. - arg2: Second HID command argument. + cmdcode: A byte long HID command. + arg1: First HID command argument. + arg2: Second HID command argument. Returns: A list containing the full HID command. @@ -87,62 +85,86 @@ def hidcmd(cls, cmdcode, arg1, arg2): # Do not count the last 4 bytes (checksum) length = [14] - cmdcode = [byte for byte in bytearray(struct.pack('=B', cmdcode))] - arg1 = [byte for byte in bytearray(struct.pack('=I', arg1))] - arg2 = [byte for byte in bytearray(struct.pack('=I', arg2))] + # Construct the command + cmdcode = list(struct.pack('=B', cmdcode)) + arg1 = list(struct.pack('=I', arg1)) + arg2 = list(struct.pack('=I', arg2)) cmd = cmdcode + length + arg1 + arg2 + cls.hid_signature - return cmd + [byte for byte in cal_checksum(cmd)] - def attach(self): - """Opens the USB device. + # Return the command with checksum tacked at the end + return cmd + list(struct.pack('=I', sum(cmd))) - Opens the device and retrieves the device attributes. + def connect(self): + """Connects the USB device. + + Connects the device and saves the USB device info attributes. """ self.device.open(self.vid, self.pid) - self.manufacturer = self.device.get_manufacturer_string() - self.product = self.device.get_product_string() - self.serial = self.device.get_serial_number_string() + if not self.manufacturer: + self.manufacturer = self.device.get_manufacturer_string() + self.product = self.device.get_product_string() + self.serial = self.device.get_serial_number_string() def get_sys_data(self): - """Reads the device data flash + """Reads the device data flash. + + ldrom attribute will be set to to True if the device is in LDROM. - Writes the HID command for reading the data flash - and retrieves the data flash to the data_flash attribute as an instance - of DataFlash. - Sets the ldrom attribute to True if the device is booted to LDROM. + Returns: + A tuple containing the data flash and its checksum. """ start = 0 end = 2048 + # Send the command for reading the data flash read_df = self.hidcmd(0x35, start, end) self.write(read_df) - self.data_flash = DataFlash(self.read(end)) + # Read the dataflash + buf = self.read(end) + dataflash = DataFlash(buf[4:], 0) + + # Something is wrong, try re-reading + if dataflash.unknown1 or not dataflash.fw_version: + self.write(read_df) + buf = self.read(end) + dataflash = DataFlash(buf[4:], 0) + + # Get the checksum from the beginning of the data flash transfer + checksum = struct.unpack('=I', bytearray(buf[0:4]))[0] + + # Are we booted to LDROM? + self.ldrom = dataflash.fw_version == 0 - self.ldrom = self.data_flash.fw_version == 0 + return (dataflash, checksum) def write(self, data): """Writes data to the device. Args: - data: A list containing binary data. + data: An iterable containing the binary data. Raises: IOError: Incorrect amount of bytes was written. """ bytes_written = 0 + + # Split the data into 64 byte long chunks chunks = [data[i:i+64] for i in range(0, len(data), 64)] + + # Write the chunks to the device for chunk in chunks: - buf = [0] + chunk + buf = [0] + chunk # First byte is the report number bytes_written += self.device.write(buf) - 1 # Windows always writes full pages if bytes_written > len(data): bytes_written -= 64 - (len(data) % 64) + # Raise IOerror if the amount sent doesn't match what we wanted if bytes_written != len(data): raise IOError("HID Write failed.") @@ -150,10 +172,10 @@ def read(self, length): """Reads data from the device. Args: - length: Amount of bytes bytes to read. + length: Amount of bytes to read. Returns: - A list containing binary data. + A list containing the binary data. Raises: IOError: Incorrect amount of bytes was read. @@ -170,47 +192,49 @@ def read(self, length): if len(data) > length: data = data[:length] + # Raise IOerror if the amount read doesn't match what we wanted if len(data) != length: raise IOError("HID read failed") return data - def set_sys_data(self, data_flash): - """Writes the device data flash. - - Writes the HID command for writing the data flash - and writes the first 2048 bytes from the data_flash argument - to the device data flash. + def set_sys_data(self, dataflash): + """Writes the data flash to the device. Args: - data_flash: A DataFlash object. + dataflash: A DataFlash object. """ + # We want 2048 bytes start = 0 end = 2048 + # Send the command for writing the data flash write_df = self.hidcmd(0x53, start, end) self.write(write_df) - self.write(list(data_flash.data)) + # Add checksum of the data in front of it + buf = list(struct.pack("=I", sum(dataflash.array))) + dataflash.array + + self.write(buf) def reset_system(self): - """Sends the HID command for resetting the system (0xB4) + """Sends the HID command for resetting the system (0xB4)""" - """ reset = self.hidcmd(0xB4, 0, 0) self.write(reset) def upload_aprom(self, aprom): - """Writes APROM to the the device. + """Writes the APROM to the the device. Args: - aprom: A BinFile object containing unencrypted APROM image. + aprom: A BinFile object containing an unencrypted APROM image. """ start = 0 end = len(aprom.data) + # Send the command for writing the APROM write_aprom = self.hidcmd(0xC3, start, end) self.write(write_aprom) diff --git a/evic/helpers.py b/evic/helpers.py deleted file mode 100644 index 1e77cfd..0000000 --- a/evic/helpers.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. -Copyright © Jussi Timperi - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -""" - -import struct - - -def cal_checksum(data): - """Calculates a checksum for the data. - - Args: - data: An iterable that can be summed. - - Returns: - A bytearray containing 4 byte integer. - """ - - return bytearray(struct.pack("=I", sum(data))) diff --git a/setup.py b/setup.py index 1492bd4..44a7179 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along """ + import ez_setup ez_setup.use_setuptools() @@ -25,8 +26,9 @@ with open('README.rst') as readme_file: readme = readme_file.read() -requirements = [ +REQUIREMENTS = [ 'hidapi>=0.7.99', + 'binstruct', 'click' ] @@ -37,10 +39,10 @@ author_email="jussi.timperi@iki.fi", description=("Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini."), license="GPL", - keywords="ecig electronic cigarette evic joyetech presa", + keywords="ecig electronic cigarette evic joyetech presa wismec", url="https://github.com/Ban3/python-evic", packages=['evic'], - install_requires=requirements, + install_requires=REQUIREMENTS, long_description=readme, classifiers=[ "Development Status :: 4 - Beta", From 6c028c66ff8a381cfeceb520cea805720ed546bb Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 25 Jan 2016 16:10:50 +0200 Subject: [PATCH 27/69] Change some Joye terminology to something that's more sensible --- evic/__init__.py | 1 - evic/aprom.py | 10 +++---- evic/cli.py | 25 ++++++++-------- evic/dataflash.py | 4 +-- evic/device.py | 75 ++++++++++++++++++++++++----------------------- 5 files changed, 58 insertions(+), 57 deletions(-) diff --git a/evic/__init__.py b/evic/__init__.py index 4c1a964..fa1c3a6 100644 --- a/evic/__init__.py +++ b/evic/__init__.py @@ -21,6 +21,5 @@ from .device import HIDTransfer -from .helpers import cal_checksum from .aprom import APROM, APROMError from .dataflash import DataFlash, DataFlashError diff --git a/evic/aprom.py b/evic/aprom.py index 02c45b6..48fe0b7 100644 --- a/evic/aprom.py +++ b/evic/aprom.py @@ -60,7 +60,7 @@ def convert(self): self._genfun(len(self.data), i)) & 0xFF return data - def verify(self, product_names, hw_version): + def verify(self, product_ids, hw_version): """Verifies the contained data. Data needs to be unencrypted. @@ -81,12 +81,12 @@ def verify(self, product_names, hw_version): id_ind = 0 max_hw_version = 0 # Try to locate supported product IDs - for product_name in product_names: + for product_id in product_ids: try: - product_name = product_name.encode() - id_ind = self.data.index(product_name) + product_id = product_id.encode() + id_ind = self.data.index(product_id) # Maximum hardware version follows the product ID - max_hw_ind = id_ind + len(product_name) + max_hw_ind = id_ind + len(product_id) max_hw_version = struct.unpack("=I", b'\x00' + self.data[max_hw_ind:max_hw_ind+3])[0] break diff --git a/evic/cli.py b/evic/cli.py index 7c68815..7f572b2 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -96,7 +96,7 @@ def read_dataflash(dev, verify): # Read the data flash with handle_exceptions(IOError): click.echo("Reading data flash...", nl=False) - dataflash, checksum = dev.get_sys_data() + dataflash, checksum = dev.read_dataflash() # Verify the data flash if verify: @@ -113,12 +113,13 @@ def print_device_info(dev, dataflash): dataflash: evic.DataFlash object. """ - # Find the device name - devicename = dev.device_names.get(dataflash.device_name, "Unknown device") + # Find the product name + product_name = dev.product_names.get(dataflash.product_id, + "Unknown device") # Print out the information click.echo("\tDevice name: ", nl=False) - click.secho(devicename, bold=True) + click.secho(product_name, bold=True) click.echo("\tFirmware version: ", nl=False) click.secho("{0:.2f}".format(dataflash.fw_version / 100.0), bold=True) click.echo("\tHardware version: ", nl=False) @@ -179,15 +180,15 @@ def upload(inputfile, encrypted, dataflashfile, noverify): with handle_exceptions(evic.APROMError): click.echo("Verifying APROM...", nl=False) aprom.verify( - dev.supported_device_names[dataflash.device_name], + dev.supported_product_ids[dataflash.product_id], dataflash.hw_version) # Are we using a data flash file? if dataflashfile: - buf = list(dataflashfile.read()) + buf = bytearray(dataflashfile.read()) # We used to store the checksum inside the file if len(buf) == 2048: - checksum = struct.unpack("=I", bytearray(buf[0:4]))[0] + checksum = struct.unpack("=I", buf[0:4])[0] dataflash = evic.DataFlash(buf[4:], 0) else: checksum = sum(buf) @@ -199,7 +200,7 @@ def upload(inputfile, encrypted, dataflashfile, noverify): dataflash.bootflag = 1 # Flashing Presa firmware requires HW version <=1.03 on type A devices - if b'W007' in aprom.data and dataflash.device_name == 'E052' \ + if b'W007' in aprom.data and dataflash.product_id == 'E052' \ and dataflash.hw_version in [106, 108, 109, 111]: click.echo("Changing HW version to 1.03...", nl=False) dataflash.hw_version = 103 @@ -208,14 +209,14 @@ def upload(inputfile, encrypted, dataflashfile, noverify): # Write data flash to the device with handle_exceptions(IOError): click.echo("Writing data flash...", nl=False) - dev.set_sys_data(dataflash) + dev.write_dataflash(dataflash) click.secho("OK", fg='green', bold=True) # We should only restart if we're not in LDROM if not dev.ldrom: # Restart click.echo("Restarting the device...", nl=False) - dev.reset_system() + dev.reset() sleep(2) click.secho("OK", fg='green', nl=False, bold=True) # Reconnect @@ -223,7 +224,7 @@ def upload(inputfile, encrypted, dataflashfile, noverify): # Write APROM to the device click.echo("Writing APROM...", nl=False) - dev.upload_aprom(aprom) + dev.write_aprom(aprom) @main.command('dump-dataflash') @@ -250,7 +251,7 @@ def dumpdataflash(output, noverify): # Write the data flash to the file with handle_exceptions(IOError): click.echo("Writing data flash to the file...", nl=False) - output.write(bytearray(dataflash.array)) + output.write(dataflash.array) @main.command() diff --git a/evic/dataflash.py b/evic/dataflash.py index b15d577..1093128 100644 --- a/evic/dataflash.py +++ b/evic/dataflash.py @@ -35,7 +35,7 @@ class DataFlash(binstruct.StructTemplate): when the device is restarted. 0 = APROM 1 = LDROM - device_name: Device name string. + product_id: Product ID string. fw_version: An integer firmware version number. unknown1: TODO unknown2: TODO @@ -43,7 +43,7 @@ class DataFlash(binstruct.StructTemplate): hw_version = binstruct.Int32Field(4) bootflag = binstruct.Int8Field(9) - device_name = binstruct.StringField(312, 4) + product_id = binstruct.StringField(312, 4) fw_version = binstruct.Int32Field(256) unknown1 = binstruct.Int32Field(260) unknown2 = binstruct.Int32Field(264) diff --git a/evic/device.py b/evic/device.py index 9113bc4..de4a2dc 100644 --- a/evic/device.py +++ b/evic/device.py @@ -30,11 +30,11 @@ class HIDTransfer(object): Attributes: vid: USB vendor ID. pid: USB product ID. - device_names: A dictionary mapping product IDs to - device name strings. - supported_device_names: A dictionary mapping device name to a list of - strings containing the names of - the products with compatible firmware + product_names: A dictionary mapping product IDs to + product name strings. + supported_product_ids: A dictionary mapping product ID to a list of + strings containing the IDs of the products + with compatible firmware. hid_signature: A list containing the HID command signature (4 bytes). device: A HIDAPI device. manufacturer: A string containing the device manufacturer. @@ -45,22 +45,22 @@ class HIDTransfer(object): vid = 0x0416 pid = 0x5020 - device_names = {'E052': "eVic-VTC Mini", - 'E060': "Cuboid", - 'W007': "Presa TC75W", - 'W010': "Classic", - 'W011': "Lite", - 'W013': "Stout", - 'W014': "Reuleaux RX200"} - supported_device_names = {'E052': ['E052', 'W007'], - 'E060': ['E060'], - 'W007': ['W007'], - 'W010': ['W010'], - 'W011': ['W011'], - 'W013': ['W013'], - 'W014': ['W014']} + product_names = {'E052': "eVic-VTC Mini", + 'E060': "Cuboid", + 'W007': "Presa TC75W", + 'W010': "Classic", + 'W011': "Lite", + 'W013': "Stout", + 'W014': "Reuleaux RX200"} + supported_product_ids = {'E052': ['E052', 'W007'], + 'E060': ['E060'], + 'W007': ['W007'], + 'W010': ['W010'], + 'W011': ['W011'], + 'W013': ['W013'], + 'W014': ['W014']} # 0x43444948 - hid_signature = [0x48, 0x49, 0x44, 0x43] + hid_signature = bytearray(b'HIDC') def __init__(self): self.device = hid.device() @@ -79,20 +79,20 @@ def hidcmd(cls, cmdcode, arg1, arg2): arg2: Second HID command argument. Returns: - A list containing the full HID command. + A bytearray containing the full HID command. """ # Do not count the last 4 bytes (checksum) - length = [14] + length = bytearray([14]) # Construct the command - cmdcode = list(struct.pack('=B', cmdcode)) - arg1 = list(struct.pack('=I', arg1)) - arg2 = list(struct.pack('=I', arg2)) + cmdcode = bytearray(struct.pack('=B', cmdcode)) + arg1 = bytearray(struct.pack('=I', arg1)) + arg2 = bytearray(struct.pack('=I', arg2)) cmd = cmdcode + length + arg1 + arg2 + cls.hid_signature # Return the command with checksum tacked at the end - return cmd + list(struct.pack('=I', sum(cmd))) + return cmd + bytearray(struct.pack('=I', sum(cmd))) def connect(self): """Connects the USB device. @@ -106,7 +106,7 @@ def connect(self): self.product = self.device.get_product_string() self.serial = self.device.get_serial_number_string() - def get_sys_data(self): + def read_dataflash(self): """Reads the device data flash. ldrom attribute will be set to to True if the device is in LDROM. @@ -133,7 +133,7 @@ def get_sys_data(self): dataflash = DataFlash(buf[4:], 0) # Get the checksum from the beginning of the data flash transfer - checksum = struct.unpack('=I', bytearray(buf[0:4]))[0] + checksum = struct.unpack('=I', buf[0:4])[0] # Are we booted to LDROM? self.ldrom = dataflash.fw_version == 0 @@ -153,11 +153,11 @@ def write(self, data): bytes_written = 0 # Split the data into 64 byte long chunks - chunks = [data[i:i+64] for i in range(0, len(data), 64)] + chunks = [bytearray(data[i:i+64]) for i in range(0, len(data), 64)] # Write the chunks to the device for chunk in chunks: - buf = [0] + chunk # First byte is the report number + buf = bytearray([0]) + chunk # First byte is the report number bytes_written += self.device.write(buf) - 1 # Windows always writes full pages @@ -175,7 +175,7 @@ def read(self, length): length: Amount of bytes to read. Returns: - A list containing the binary data. + A bytearray containing the binary data. Raises: IOError: Incorrect amount of bytes was read. @@ -196,9 +196,9 @@ def read(self, length): if len(data) != length: raise IOError("HID read failed") - return data + return bytearray(data) - def set_sys_data(self, dataflash): + def write_dataflash(self, dataflash): """Writes the data flash to the device. Args: @@ -214,17 +214,18 @@ def set_sys_data(self, dataflash): self.write(write_df) # Add checksum of the data in front of it - buf = list(struct.pack("=I", sum(dataflash.array))) + dataflash.array + buf = bytearray(struct.pack("=I", sum(dataflash.array))) + \ + dataflash.array self.write(buf) - def reset_system(self): + def reset(self): """Sends the HID command for resetting the system (0xB4)""" reset = self.hidcmd(0xB4, 0, 0) self.write(reset) - def upload_aprom(self, aprom): + def write_aprom(self, aprom): """Writes the APROM to the the device. Args: @@ -238,4 +239,4 @@ def upload_aprom(self, aprom): write_aprom = self.hidcmd(0xC3, start, end) self.write(write_aprom) - self.write(list(aprom.data)) + self.write(aprom.data) From 7ca74be5e81b07ad196cb942b77ca1aae491217d Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 25 Jan 2016 16:28:44 +0200 Subject: [PATCH 28/69] Update comments --- evic/device.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evic/device.py b/evic/device.py index de4a2dc..2107e93 100644 --- a/evic/device.py +++ b/evic/device.py @@ -35,7 +35,8 @@ class HIDTransfer(object): supported_product_ids: A dictionary mapping product ID to a list of strings containing the IDs of the products with compatible firmware. - hid_signature: A list containing the HID command signature (4 bytes). + hid_signature: A bytearray containing the HID command signature + (4 bytes). device: A HIDAPI device. manufacturer: A string containing the device manufacturer. product: A string containing the product name. From 91cb65c7b829445abc5d27792dee3fa7c63d1152 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 25 Jan 2016 16:29:48 +0200 Subject: [PATCH 29/69] Ignore ez_setup.py from code checking --- .checkignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .checkignore diff --git a/.checkignore b/.checkignore new file mode 100644 index 0000000..c899ef4 --- /dev/null +++ b/.checkignore @@ -0,0 +1 @@ +ez_setup.py From 76989914732fbe8fb9b2fbd689283c663909cbb4 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Tue, 26 Jan 2016 17:10:12 +0200 Subject: [PATCH 30/69] Add basic unit testing --- .travis.yml | 36 ++++++++++++++++++++ MANIFEST.in | 5 ++- Makefile | 14 +++++++- setup.cfg | 5 +++ setup.py | 3 ++ testdata/helloworld.bin | Bin 0 -> 12028 bytes testdata/src/helloworld/Makefile | 5 +++ testdata/src/helloworld/main.c | 30 +++++++++++++++++ testdata/test_dataflash.bin | Bin 0 -> 2044 bytes tests/test_aprom.py | 44 +++++++++++++++++++++++++ tests/test_cli.py | 49 +++++++++++++++++++++++++++ tests/test_dataflash.py | 55 +++++++++++++++++++++++++++++++ tests/test_device.py | 34 +++++++++++++++++++ tox.ini | 9 +++++ 14 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 .travis.yml create mode 100644 setup.cfg create mode 100644 testdata/helloworld.bin create mode 100644 testdata/src/helloworld/Makefile create mode 100644 testdata/src/helloworld/main.c create mode 100644 testdata/test_dataflash.bin create mode 100644 tests/test_aprom.py create mode 100644 tests/test_cli.py create mode 100644 tests/test_dataflash.py create mode 100644 tests/test_device.py create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4fba5b1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,36 @@ +env: +- TOXENV=py35 +- TOXENV=py34 +- TOXENV=py33 +- TOXENV=py27 +- TOXENV=py26 +- TOXENV=pypy + +install: pip install -U tox + +language: python + +script: tox + +deploy: + provider: pypi + password: + secure: !!binary | + VHNRS0I3QlFmTFlzVVhJQk5zM1JUWUxjUkpidU5ZMk0vYS9PM3NuYjMyV2sxQm9uTkk4UzIzT1Zh + cFZnUnpZdDdaTU9xRlJDWmF0VENRWnlPRE9vSldqa1BXQ3lMNVZXODJWVWlrcEM3TjhES3ByOEM4 + L2xITkNaeHJhNmtWY3Y4S21PWVZaNUJCSksvY2dGQ2JJYURPN2E2L3E2SmZoRG80YmJLcDAwbmZX + ejh1d1I5ejg2V2FCbm9aQVdrOSs3ZGc2MW43WXJHaU9mSGozRFhLZWdyUlhmMHBNcTRVWUJjZTAv + eFlDWHdYcFNMQnNMZUZvU0p1bmczeDhGNWY2WXcwSWRzaXFFTnVENmQvYkZzK1JYaEd2REQ1RjJF + ZXMycnYrM3VVZzZwUEJMWk1mbjBTeGEwMDNkWG1YczhhSXFRL2hzR2oxNTl3MjE2MW5nNzJaRDlq + YVJuV2Z5RDhJdzZpWm9XeHh6ei9kaXJ5VVU5NVBRdUN1UzI0dndaZ1ZNVXp2SXB2RG5nV2JYYWwy + K05PcnNwdUtEeVpQR2trTmxlUUVGcS92MkY4NFY4aGtPNDV3TStmRGlCVkNuQmtpUUQ4aEhzRTB5 + TUpCSFpjRTM1ajZPdHlwVHBtMXJ5aXhlSFpxcXo2ckNGVVM3MzNjaTNTZi9RWndodklMMGZ6L29Q + YUllZlRZYmZ6WXVmWEgxaU5QNEJpMGxpZjVBVnpMZTZYZGdjY3VzY25OK2c4MWZHeVB2MlgxR3pD + S2pCclpSTWZFOHVhK3I0MnZkOU01L2R2dkM3dzFIV3ZMSVRvTHZyeVlrd0lPdU94L0dudFRxN3pP + c1licDhiOU1UMkplbXkzR0daa0ZNNlkwWnhsRWZKMGNHdVFXNnY3ZDUyZEZ2VUE1UDM5Q2J6b1E9 + user: Ban3 + distributions: sdist bdist_wheel + on: + condition: $TOXENV == py27 + repo: Ban3/python-evic + tags: true diff --git a/MANIFEST.in b/MANIFEST.in index d7ab55f..06b6e8f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,10 @@ include LICENSE include README.rst +include Makefile recursive-exclude * __pycache__ recursive-exclude * *.py[co] -recursive-include * *.rst Makefile +recursive-include * *.rst + +recursive-include udev * diff --git a/Makefile b/Makefile index c2daaef..ab4678f 100644 --- a/Makefile +++ b/Makefile @@ -4,12 +4,15 @@ help: @echo "clean - remove all build, test, coverage and Python artifacts" @echo "clean-build - remove build artifacts" @echo "clean-pyc - remove Python file artifacts" + @echo "clean-test - remove test artifacts" @echo "lint - check style with flake8" + @echo "test - run tests quickly with the default Python" + @echo "test-all - run tests on every Python version with tox" @echo "release - package and upload a release" @echo "dist - package" @echo "install - install the package to the active Python's site-packages" -clean: clean-build clean-pyc +clean: clean-build clean-pyc clean-test clean-build: rm -fr build/ @@ -24,9 +27,18 @@ clean-pyc: find . -name '*~' -exec rm -f {} + find . -name '__pycache__' -exec rm -fr {} + +clean-test: + rm -fr .tox/ + lint: flake8 evic tests +test: + python setup.py test + +test-all: + tox + release: clean python setup.py sdist upload python setup.py bdist_wheel upload diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f05f933 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[aliases] +test=pytest + +[bdist_wheel] +universal=1 diff --git a/setup.py b/setup.py index 44a7179..23c604d 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,10 @@ keywords="ecig electronic cigarette evic joyetech presa wismec", url="https://github.com/Ban3/python-evic", packages=['evic'], + setup_requires=['pytest-runner'], + tests_require=['pytest'], install_requires=REQUIREMENTS, + data_files=[('udev', ['udev/99-nuvoton-hid.rules'])], long_description=readme, classifiers=[ "Development Status :: 4 - Beta", diff --git a/testdata/helloworld.bin b/testdata/helloworld.bin new file mode 100644 index 0000000000000000000000000000000000000000..92d6858781d313895037c6a4445ae4ffe4cc0e3f GIT binary patch literal 12028 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 I{+oaPA8b!oXaE2J literal 0 HcmV?d00001 diff --git a/testdata/src/helloworld/Makefile b/testdata/src/helloworld/Makefile new file mode 100644 index 0000000..72ce5c6 --- /dev/null +++ b/testdata/src/helloworld/Makefile @@ -0,0 +1,5 @@ +TARGET = helloworld + +OBJS = main.o + +include $(EVICSDK)/make/Base.mk diff --git a/testdata/src/helloworld/main.c b/testdata/src/helloworld/main.c new file mode 100644 index 0000000..147ca9d --- /dev/null +++ b/testdata/src/helloworld/main.c @@ -0,0 +1,30 @@ +/* + * This file is part of eVic SDK. + * + * eVic SDK is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * eVic SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with eVic SDK. If not, see . + * + * Copyright (C) 2015-2016 ReservedField + */ + +#include +#include +#include + +int main() { + // Blit text + Display_PutText(8, 56, "Hello,\nWorld.", FONT_DEJAVU_8PT); + + // Update display + Display_Update(); +} diff --git a/testdata/test_dataflash.bin b/testdata/test_dataflash.bin new file mode 100644 index 0000000000000000000000000000000000000000..77a7410e27f83a72e5c654a13c3c090b52457e87 GIT binary patch literal 2044 zcmaF-nSmjlfq}t@fra4-!)u0jOuS4Cj4X^5K(Q7eW@O}I5MfYeuwe*b$YZErXkzGL zn8vVxVHFl=%76*Y7|fy2f&l@6217w50|OHS&|DB42%y6VvGo>^YS0AwH>icdkb#|n z^`i=Ng%Jb8e>A|=&cJZmgMq=-z|@G55vqA096oC2Xb6mkz-S1JhQMeDkQo91LHRB* literal 0 HcmV?d00001 diff --git a/tests/test_aprom.py b/tests/test_aprom.py new file mode 100644 index 0000000..90f3f99 --- /dev/null +++ b/tests/test_aprom.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +""" +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. +Copyright © Jussi Timperi + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +import pytest + +import evic + + +class TestAPROM: + + def test_aprom_convert(self): + with open("testdata/helloworld.bin", "rb") as apromfile: + aprom_data = bytearray(apromfile.read()) + aprom = evic.APROM(aprom_data) + aprom_unencrypted = evic.APROM(aprom.convert()) + + assert aprom_data == aprom_unencrypted.convert() + + def test_aprom_verify(self): + with open("testdata/helloworld.bin", "rb") as apromfile: + aprom = evic.APROM(apromfile.read()) + aprom_unencrypted = evic.APROM(aprom.convert()) + + aprom_unencrypted.verify(['E052'], 106) + + with pytest.raises(evic.APROMError): + aprom_unencrypted.verify(['W007'], 106) + aprom_unencrypted.verify(['E052'], 999) diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..0ded624 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +""" +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. +Copyright © Jussi Timperi + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +from click.testing import CliRunner + +from evic import cli + + +class TestCli: + + def test_cli_convert(self): + with open('testdata/helloworld.bin', 'rb') as apromfile: + aprom_data = apromfile.read() + + runner = CliRunner() + with runner.isolated_filesystem(): + with open('test_aprom.bin', 'wb') as apromfile: + apromfile.write(aprom_data) + + result = runner.invoke(cli.main, ['convert', + 'test_aprom.bin', + '-o', + 'test_aprom_unencrypted.bin']) + assert result.exit_code == 0 + + result = runner.invoke(cli.main, ['convert', + 'test_aprom_unencrypted.bin', + '-o', + 'test_aprom2.bin']) + assert result.exit_code == 0 + + with open('test_aprom2.bin', 'rb') as apromfile: + assert aprom_data == apromfile.read() diff --git a/tests/test_dataflash.py b/tests/test_dataflash.py new file mode 100644 index 0000000..86a1756 --- /dev/null +++ b/tests/test_dataflash.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +""" +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. +Copyright © Jussi Timperi + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +import struct +import pytest + +import evic + + +class TestDataFlash: + + def test_dataflash_from_file(self): + with open("testdata/test_dataflash.bin", "rb") as dataflashfile: + dataflash = evic.DataFlash(bytearray(dataflashfile.read()), 0) + assert dataflash.hw_version == 103 + assert dataflash.bootflag == 0 + assert dataflash.product_id == "E052" + assert dataflash.fw_version == 300 + + def test_set_dataflash_attributes(self): + with open("testdata/test_dataflash.bin", "rb") as dataflashfile: + dataflash = evic.DataFlash(bytearray(dataflashfile.read()), 0) + dataflash.bootflag = 1 + dataflash.hw_version = 106 + + assert dataflash.bootflag == 1 + assert dataflash.array[9] == 1 + assert dataflash.hw_version == 106 + assert struct.unpack("=I", dataflash.array[4:8])[0] == 106 + + def test_dataflash_verify(self): + with open("testdata/test_dataflash.bin", "rb") as dataflashfile: + dataflash = evic.DataFlash(bytearray(dataflashfile.read()), 0) + checksum = sum(dataflash.array) + + dataflash.verify(checksum) + + with pytest.raises(evic.DataFlashError): + dataflash.verify(0) diff --git a/tests/test_device.py b/tests/test_device.py new file mode 100644 index 0000000..cac0279 --- /dev/null +++ b/tests/test_device.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +""" +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. +Copyright © Jussi Timperi + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +import evic + + +class TestDevice: + + def test_hidtransfer_hidcmd(self): + read_df_cmd = bytearray(b'5\x0e\x00\x00\x00\x00\x00\x08\x00\x00HIDCc\x01\x00\x00') + write_df_cmd = bytearray(b'S\x0e\x00\x00\x00\x00\x00\x08\x00\x00HIDC\x81\x01\x00\x00') + reset_cmd = bytearray(b'\xb4\x0e\x00\x00\x00\x00\x00\x00\x00\x00HIDC\xda\x01\x00\x00') + write_aprom_cmd = bytearray(b'\xc3\x0e\x00\x00\x00\x00\x00\x00\x00\x00HIDC\xe9\x01\x00\x00') + + assert evic.HIDTransfer.hidcmd(0xC3, 0, 0) == write_aprom_cmd + assert evic.HIDTransfer.hidcmd(0xB4, 0, 0) == reset_cmd + assert evic.HIDTransfer.hidcmd(0x35, 0, 2048) == read_df_cmd + assert evic.HIDTransfer.hidcmd(0x53, 0, 2048) == write_df_cmd diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..f66b93b --- /dev/null +++ b/tox.ini @@ -0,0 +1,9 @@ +[tox] +envlist = py26, py27, py33, py34, py35 + +[testenv] +deps = + setuptools>=19.6 +setenv = + PYTHONPATH = {toxinidir}:{toxinidir}/evic +commands = python setup.py test From f058474e770d802a9706ed43a78f078b1078706a Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Tue, 26 Jan 2016 17:23:52 +0200 Subject: [PATCH 31/69] Try to make travis happy --- .travis.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4fba5b1..929b2b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,10 +6,24 @@ env: - TOXENV=py26 - TOXENV=pypy +addons: + apt: + packages: + - libudev-dev + - libusb-1.0-0-dev + install: pip install -U tox language: python +python: + - "2.6" + - "2.7" + - "3.2" + - "3.3" + - "3.4" + - "3.5" + script: tox deploy: From 343e3f5240af0eac0bdd97e55bafdbb7c86b8c90 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Tue, 26 Jan 2016 17:37:37 +0200 Subject: [PATCH 32/69] Actually fix the travis build --- .travis.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 929b2b5..71caa7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ env: -- TOXENV=py35 - TOXENV=py34 - TOXENV=py33 - TOXENV=py27 @@ -16,15 +15,16 @@ install: pip install -U tox language: python -python: - - "2.6" - - "2.7" - - "3.2" - - "3.3" - - "3.4" - - "3.5" +matrix: + include: + - python: 3.5 + env: TOXENV=py35 script: tox +cache: + directories: + - .tox + - $HOME/.cache/pip deploy: provider: pypi From ca3d803123b3dffb00473472f75705d79c1656e6 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Tue, 26 Jan 2016 19:54:27 +0200 Subject: [PATCH 33/69] Python 2.6 doesn't support unpacking bytearrays --- evic/aprom.py | 4 ++-- evic/cli.py | 2 +- evic/device.py | 2 +- tests/test_dataflash.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/evic/aprom.py b/evic/aprom.py index 48fe0b7..de945b5 100644 --- a/evic/aprom.py +++ b/evic/aprom.py @@ -87,8 +87,8 @@ def verify(self, product_ids, hw_version): id_ind = self.data.index(product_id) # Maximum hardware version follows the product ID max_hw_ind = id_ind + len(product_id) - max_hw_version = struct.unpack("=I", b'\x00' + - self.data[max_hw_ind:max_hw_ind+3])[0] + max_hw_version = struct.unpack("=I", bytes(b'\x00' + + self.data[max_hw_ind:max_hw_ind+3]))[0] break # Product ID was not found, try the next one except ValueError: diff --git a/evic/cli.py b/evic/cli.py index 7f572b2..f816265 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -188,7 +188,7 @@ def upload(inputfile, encrypted, dataflashfile, noverify): buf = bytearray(dataflashfile.read()) # We used to store the checksum inside the file if len(buf) == 2048: - checksum = struct.unpack("=I", buf[0:4])[0] + checksum = struct.unpack("=I", bytes(buf[0:4]))[0] dataflash = evic.DataFlash(buf[4:], 0) else: checksum = sum(buf) diff --git a/evic/device.py b/evic/device.py index 2107e93..abdabd4 100644 --- a/evic/device.py +++ b/evic/device.py @@ -134,7 +134,7 @@ def read_dataflash(self): dataflash = DataFlash(buf[4:], 0) # Get the checksum from the beginning of the data flash transfer - checksum = struct.unpack('=I', buf[0:4])[0] + checksum = struct.unpack('=I', bytes(buf[0:4]))[0] # Are we booted to LDROM? self.ldrom = dataflash.fw_version == 0 diff --git a/tests/test_dataflash.py b/tests/test_dataflash.py index 86a1756..8993a16 100644 --- a/tests/test_dataflash.py +++ b/tests/test_dataflash.py @@ -42,7 +42,7 @@ def test_set_dataflash_attributes(self): assert dataflash.bootflag == 1 assert dataflash.array[9] == 1 assert dataflash.hw_version == 106 - assert struct.unpack("=I", dataflash.array[4:8])[0] == 106 + assert struct.unpack("=I", bytes(dataflash.array[4:8]))[0] == 106 def test_dataflash_verify(self): with open("testdata/test_dataflash.bin", "rb") as dataflashfile: From 1a3e3aba493401c8bba9b67d51b3f246e33e25d9 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Tue, 26 Jan 2016 19:55:54 +0200 Subject: [PATCH 34/69] Update more comments --- evic/aprom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evic/aprom.py b/evic/aprom.py index de945b5..2f78aa4 100644 --- a/evic/aprom.py +++ b/evic/aprom.py @@ -66,7 +66,7 @@ def verify(self, product_ids, hw_version): Data needs to be unencrypted. Args: - product_names: A list of supported product names for the device. + product_ids: A list of supported product IDs for the device. hw_version: An integer device hardware version. Raises: From 3d78dd2c9ce775db5d8f6c73af6cccd0d79c50ea Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Tue, 26 Jan 2016 19:58:54 +0200 Subject: [PATCH 35/69] Add travis badge --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 78aec45..03da6b9 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,9 @@ Evic =============================== +.. image:: https://travis-ci.org/Ban3/python-evic.svg?branch=master + :target: https://travis-ci.org/Ban3/python-evic + Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. Supported devices From cd52faf30551a7991b415a1ab1ccabd9f92e6825 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Thu, 4 Feb 2016 00:29:34 +0200 Subject: [PATCH 36/69] Only bootstrap setuptools if they're not available --- setup.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 23c604d..5a97e2c 100644 --- a/setup.py +++ b/setup.py @@ -18,10 +18,13 @@ along """ -import ez_setup -ez_setup.use_setuptools() +try: + from setuptools import setup +except ImportError: + import ez_setup + ez_setup.use_setuptools() + from setuptools import setup -from setuptools import setup with open('README.rst') as readme_file: readme = readme_file.read() From 0763cb2dabc7996a59b4743a4cf29364b0dfcd71 Mon Sep 17 00:00:00 2001 From: ReservedField Date: Wed, 3 Feb 2016 23:30:29 +0100 Subject: [PATCH 37/69] Only write dataflash if it has been changed --- evic/cli.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/evic/cli.py b/evic/cli.py index f816265..cfe8c9d 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -19,6 +19,7 @@ import sys +import copy import struct from time import sleep from contextlib import contextmanager @@ -166,6 +167,7 @@ def upload(inputfile, encrypted, dataflashfile, noverify): # Read the data flash verify = 'dataflash' not in noverify dataflash = read_dataflash(dev, verify) + dataflash_original = copy.deepcopy(dataflash) # Print the device information print_device_info(dev, dataflash) @@ -197,7 +199,8 @@ def upload(inputfile, encrypted, dataflashfile, noverify): verify_dataflash(dataflash, checksum) # We want to boot to LDROM on restart - dataflash.bootflag = 1 + if not dev.ldrom: + dataflash.bootflag = 1 # Flashing Presa firmware requires HW version <=1.03 on type A devices if b'W007' in aprom.data and dataflash.product_id == 'E052' \ @@ -208,9 +211,10 @@ def upload(inputfile, encrypted, dataflashfile, noverify): # Write data flash to the device with handle_exceptions(IOError): - click.echo("Writing data flash...", nl=False) - dev.write_dataflash(dataflash) - click.secho("OK", fg='green', bold=True) + if dataflash.array != dataflash_original.array: + click.echo("Writing data flash...", nl=False) + dev.write_dataflash(dataflash) + click.secho("OK", fg='green', bold=True) # We should only restart if we're not in LDROM if not dev.ldrom: From c18d93d7fdc87ab38e906b62ccbf3e6aae0468d6 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Sun, 7 Feb 2016 23:44:20 +0200 Subject: [PATCH 38/69] Sleep a bit before writing the data flash, just in case. --- evic/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/evic/cli.py b/evic/cli.py index cfe8c9d..2567ed6 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -213,6 +213,7 @@ def upload(inputfile, encrypted, dataflashfile, noverify): with handle_exceptions(IOError): if dataflash.array != dataflash_original.array: click.echo("Writing data flash...", nl=False) + sleep(0.1) dev.write_dataflash(dataflash) click.secho("OK", fg='green', bold=True) From 32b3edc43c29fa38f4a894e737683424a292d825 Mon Sep 17 00:00:00 2001 From: ReservedField Date: Wed, 10 Feb 2016 14:08:28 +0100 Subject: [PATCH 39/69] Add command to reset data flash --- evic/cli.py | 18 ++++++++++++++++++ evic/device.py | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/evic/cli.py b/evic/cli.py index 2567ed6..ebcd60e 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -270,3 +270,21 @@ def convert(inputfile, output): with handle_exceptions(IOError): click.echo("Writing APROM image...", nl=False) output.write(binfile.convert()) + + +@main.command('reset-dataflash') +def resetdataflash(): + """Reset device data flash.""" + + dev = evic.HIDTransfer() + + # Connect the device + connect(dev) + + # Print the USB info of the device + print_usb_info(dev) + + # Reset data flash + with handle_exceptions(IOError): + click.echo("Resetting data flash...", nl=False) + dev.reset_dataflash() diff --git a/evic/device.py b/evic/device.py index abdabd4..946a789 100644 --- a/evic/device.py +++ b/evic/device.py @@ -220,6 +220,15 @@ def write_dataflash(self, dataflash): self.write(buf) + def reset_dataflash(self): + """Resets the device data flash. + + Sends a data flash reset request to the firmware. + """ + + reset_df = self.hidcmd(0x7C, 0, 0) + self.write(reset_df) + def reset(self): """Sends the HID command for resetting the system (0xB4)""" From d9ffc949404c8ffd9e345fb8e0f4c11beca054f3 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 7 Mar 2016 13:06:17 +0200 Subject: [PATCH 40/69] User reported Cuboid as tested --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 03da6b9..aa0088c 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ Supported devices --------------------- * Evic VTC Mini -* Cuboid* +* Cuboid * Presa TC75W* * Vaporflask Classic* * Vaporflask Lite* From 0ec1f6adf7003b309faae6d5eca16c71e768581c Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 7 Mar 2016 13:30:57 +0200 Subject: [PATCH 41/69] Add iStick TC100W to supported devices --- README.rst | 1 + evic/device.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/README.rst b/README.rst index aa0088c..984b089 100644 --- a/README.rst +++ b/README.rst @@ -12,6 +12,7 @@ Supported devices * Evic VTC Mini * Cuboid +* iStick TC100W* * Presa TC75W* * Vaporflask Classic* * Vaporflask Lite* diff --git a/evic/device.py b/evic/device.py index 946a789..fb05cff 100644 --- a/evic/device.py +++ b/evic/device.py @@ -48,6 +48,7 @@ class HIDTransfer(object): pid = 0x5020 product_names = {'E052': "eVic-VTC Mini", 'E060': "Cuboid", + 'M011': "iStick TC100W", 'W007': "Presa TC75W", 'W010': "Classic", 'W011': "Lite", @@ -55,6 +56,7 @@ class HIDTransfer(object): 'W014': "Reuleaux RX200"} supported_product_ids = {'E052': ['E052', 'W007'], 'E060': ['E060'], + 'M011': ['M011'], 'W007': ['W007'], 'W010': ['W010'], 'W011': ['W011'], From 17875b010490d2c9522ec6828d037b670851cfd5 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 7 Mar 2016 13:35:30 +0200 Subject: [PATCH 42/69] Presa TC75W should support Evic firmware as is --- evic/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evic/device.py b/evic/device.py index fb05cff..f44782e 100644 --- a/evic/device.py +++ b/evic/device.py @@ -57,7 +57,7 @@ class HIDTransfer(object): supported_product_ids = {'E052': ['E052', 'W007'], 'E060': ['E060'], 'M011': ['M011'], - 'W007': ['W007'], + 'W007': ['W007', 'E052'], 'W010': ['W010'], 'W011': ['W011'], 'W013': ['W013'], From 07de3e847a4e1c91870eaac190671931c15d065f Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 7 Mar 2016 14:34:19 +0200 Subject: [PATCH 43/69] Split USB functionality from convert --- README.rst | 29 +++++++++++++++++------------ evic/cli.py | 34 +++++++++++++++++----------------- setup.py | 3 ++- tests/test_cli.py | 14 ++++++-------- 4 files changed, 42 insertions(+), 38 deletions(-) diff --git a/README.rst b/README.rst index 984b089..83e5ede 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,4 @@ + =============================== Evic =============================== @@ -37,7 +38,7 @@ Installation Install from source: ^^^^^^^^^^^^^^^^^^^^^^ -Building hidapi requires libusb headers and cython. On Arch Linux they can be obtained from the repositories by installing packages ``libusb`` and ``cython``. Debian based distributions will have packages ``libusb-1.0-0-dev`` and ``cython``. +Using ``evic-usb`` requires ``cython-hidapi``. Building ``cython-hidapi`` requires libusb headers and cython. On Arch Linux they can be obtained from the repositories by installing packages ``libusb`` and ``cython``. Debian based distributions will have packages ``libusb-1.0-0-dev`` and ``cython``. On Windows you will also need the correct compiler for your Python version. See `this `_ page for more information on setting up the compiler. @@ -62,41 +63,45 @@ Usage ------- See ``--help`` for more information on a given command. -| - -Encrypt/decrypt a firmware image: +evic-convert +^^^^^^^^^^^^ +``evic-convert`` is a tool to encrypt/decrypt firmware images: :: - $ evic convert in.bin -o out.bin + $ evic-convert in.bin -o out.bin + +evic-usb +^^^^^^^^^^^^ +``evic-usb`` is a tool for interfacing with the device through USB. + Dump device data flash to a file: :: - $ evic dump-dataflash -o out.bin + $ evic-usb dump-dataflash -o out.bin Upload an encrypted firmware image to the device: :: - $ evic upload firmware.bin + $ evic-usb upload firmware.bin Upload an unencrypted firmware image to the device: :: - $ evic upload -u firmware.bin + $ evic-usb upload -u firmware.bin Upload a firmware image using data flash from a file: :: - $ evic upload -d data.bin firmware.bin + $ evic-usb upload -d data.bin firmware.bin Use ``--no-verify`` to disable verification for APROM or data flash. To disable both: -:: - - $ evic upload --no-verify aprom --no-verify dataflash firmware.bin +:: + $ evic-usb upload --no-verify aprom --no-verify dataflash firmware.bin diff --git a/evic/cli.py b/evic/cli.py index ebcd60e..edcfc5e 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -43,7 +43,7 @@ def handle_exceptions(*exceptions): @click.group() -def main(): +def usb(): """A USB programmer for devices based on the Joyetech Evic VTC Mini.""" pass @@ -144,7 +144,7 @@ def verify_dataflash(dataflash, checksum): dataflash.verify(checksum) -@main.command() +@usb.command() @click.argument('inputfile', type=click.File('rb')) @click.option('--encrypted/--unencrypted', '-e/-u', default=True, help='Use encrypted/unencrypted image. Defaults to encrypted.') @@ -232,7 +232,7 @@ def upload(inputfile, encrypted, dataflashfile, noverify): dev.write_aprom(aprom) -@main.command('dump-dataflash') +@usb.command('dump-dataflash') @click.option('--output', '-o', type=click.File('wb')) @click.option('--no-verify', 'noverify', is_flag=True, help='Disable verification.') @@ -259,20 +259,7 @@ def dumpdataflash(output, noverify): output.write(dataflash.array) -@main.command() -@click.argument('inputfile', type=click.File('rb')) -@click.option('--output', '-o', type=click.File('wb')) -def convert(inputfile, output): - """Decrypt/encrypt an APROM image.""" - - binfile = evic.APROM(inputfile.read()) - - with handle_exceptions(IOError): - click.echo("Writing APROM image...", nl=False) - output.write(binfile.convert()) - - -@main.command('reset-dataflash') +@usb.command('reset-dataflash') def resetdataflash(): """Reset device data flash.""" @@ -288,3 +275,16 @@ def resetdataflash(): with handle_exceptions(IOError): click.echo("Resetting data flash...", nl=False) dev.reset_dataflash() + + +@click.command() +@click.argument('inputfile', type=click.File('rb')) +@click.option('--output', '-o', type=click.File('wb')) +def convert(inputfile, output): + """Decrypt/encrypt an APROM image.""" + + binfile = evic.APROM(inputfile.read()) + + with handle_exceptions(IOError): + click.echo("Writing APROM image...", nl=False) + output.write(binfile.convert()) diff --git a/setup.py b/setup.py index 5a97e2c..e7d8d86 100644 --- a/setup.py +++ b/setup.py @@ -62,6 +62,7 @@ ], entry_points={ 'console_scripts': [ - 'evic=evic.cli:main'], + 'evic-convert=evic.cli:convert', + 'evic-usb=evic.cli:usb'], }, ) diff --git a/tests/test_cli.py b/tests/test_cli.py index 0ded624..781dcdd 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -33,16 +33,14 @@ def test_cli_convert(self): with open('test_aprom.bin', 'wb') as apromfile: apromfile.write(aprom_data) - result = runner.invoke(cli.main, ['convert', - 'test_aprom.bin', - '-o', - 'test_aprom_unencrypted.bin']) + result = runner.invoke(cli.convert, ['test_aprom.bin', + '-o', + 'test_aprom_unencrypted.bin']) assert result.exit_code == 0 - result = runner.invoke(cli.main, ['convert', - 'test_aprom_unencrypted.bin', - '-o', - 'test_aprom2.bin']) + result = runner.invoke(cli.convert, ['test_aprom_unencrypted.bin', + '-o', + 'test_aprom2.bin']) assert result.exit_code == 0 with open('test_aprom2.bin', 'rb') as apromfile: From 1936d23d2e23fb819bfd7c381c15129f91678e31 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 7 Mar 2016 14:55:30 +0200 Subject: [PATCH 44/69] Make hidapi dynamic dependency for evic-usb --- .travis.yml | 6 ------ README.rst | 9 +++++++-- evic/device.py | 11 +++++++++-- setup.py | 6 ++++-- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 71caa7b..f0374cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,12 +5,6 @@ env: - TOXENV=py26 - TOXENV=pypy -addons: - apt: - packages: - - libudev-dev - - libusb-1.0-0-dev - install: pip install -U tox language: python diff --git a/README.rst b/README.rst index 83e5ede..bca8906 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,3 @@ - =============================== Evic =============================== @@ -38,7 +37,13 @@ Installation Install from source: ^^^^^^^^^^^^^^^^^^^^^^ -Using ``evic-usb`` requires ``cython-hidapi``. Building ``cython-hidapi`` requires libusb headers and cython. On Arch Linux they can be obtained from the repositories by installing packages ``libusb`` and ``cython``. Debian based distributions will have packages ``libusb-1.0-0-dev`` and ``cython``. +Using ``evic-usb`` requires ``cython-hidapi``. You can install it using ``pip``: + +:: + + $ pip install hidapi + +Building ``cython-hidapi`` requires libusb headers and cython. On Arch Linux they can be obtained from the repositories by installing packages ``libusb`` and ``cython``. Debian based distributions will have packages ``libusb-1.0-0-dev`` and ``cython``. On Windows you will also need the correct compiler for your Python version. See `this `_ page for more information on setting up the compiler. diff --git a/evic/device.py b/evic/device.py index f44782e..34f41e2 100644 --- a/evic/device.py +++ b/evic/device.py @@ -19,7 +19,11 @@ import struct -import hid +try: + import hid + HIDAPI_AVAILABLE = True +except ImportError: + HIDAPI_AVAILABLE = False from .dataflash import DataFlash @@ -66,7 +70,10 @@ class HIDTransfer(object): hid_signature = bytearray(b'HIDC') def __init__(self): - self.device = hid.device() + if HIDAPI_AVAILABLE: + self.device = hid.device() + else: + self.device = None self.manufacturer = None self.product = None self.serial = None diff --git a/setup.py b/setup.py index e7d8d86..bc19788 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,6 @@ readme = readme_file.read() REQUIREMENTS = [ - 'hidapi>=0.7.99', 'binstruct', 'click' ] @@ -60,9 +59,12 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', ], + extras_require={ + 'USB': ['hidapi>=0.7.99'], + }, entry_points={ 'console_scripts': [ 'evic-convert=evic.cli:convert', - 'evic-usb=evic.cli:usb'], + 'evic-usb=evic.cli:usb [USB]'], }, ) From 39f7e9baae2b4709594b861f546cd7a8d9708b0e Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 7 Mar 2016 15:15:41 +0200 Subject: [PATCH 45/69] Version 3.01 is now tested --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index bca8906..74c5f23 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ Supported devices Tested firmware versions ----------------------------- -* Evic VTC Mini <=3.0 +* Evic VTC Mini <=3.01 * Presa TC75W 1.02\* * Examples from `evic-sdk `_ From b305f38f51f6dfefc2e51d6533da0b3da8412314 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 7 Mar 2016 18:49:02 +0200 Subject: [PATCH 46/69] Point evic script to convert --- evic/cli.py | 9 ++++++++- setup.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/evic/cli.py b/evic/cli.py index edcfc5e..93901f8 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -277,7 +277,14 @@ def resetdataflash(): dev.reset_dataflash() -@click.command() +@click.group() +def main(): + """A USB programmer for devices based on the Joyetech Evic VTC Mini.""" + + pass + + +@main.command() @click.argument('inputfile', type=click.File('rb')) @click.option('--output', '-o', type=click.File('wb')) def convert(inputfile, output): diff --git a/setup.py b/setup.py index bc19788..66a0bb7 100644 --- a/setup.py +++ b/setup.py @@ -65,6 +65,7 @@ entry_points={ 'console_scripts': [ 'evic-convert=evic.cli:convert', + 'evic=evic.cli:main', 'evic-usb=evic.cli:usb [USB]'], }, ) From ac36c96f706171211f7ffbfd7c27eb13743c2207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Gardstr=C3=B6m?= Date: Sat, 2 Apr 2016 15:41:39 +0200 Subject: [PATCH 47/69] File keeps its permissions. --- evic/cli.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evic/cli.py b/evic/cli.py index 93901f8..7eeec54 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -19,6 +19,7 @@ import sys +import os import copy import struct from time import sleep @@ -295,3 +296,4 @@ def convert(inputfile, output): with handle_exceptions(IOError): click.echo("Writing APROM image...", nl=False) output.write(binfile.convert()) + os.chmod(output.name, os.stat(inputfile.name).st_mode) From 99473fb1a306aed0c735ec992572410cf8d1e5f8 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Fri, 22 Apr 2016 15:16:47 +0300 Subject: [PATCH 48/69] Add logo upload --- evic/__init__.py | 1 + evic/cli.py | 57 +++++++++++++++++++++++++++++ evic/device.py | 16 +++++++++ evic/logo.py | 93 ++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 1 + 5 files changed, 168 insertions(+) create mode 100644 evic/logo.py diff --git a/evic/__init__.py b/evic/__init__.py index fa1c3a6..bc42e11 100644 --- a/evic/__init__.py +++ b/evic/__init__.py @@ -23,3 +23,4 @@ from .device import HIDTransfer from .aprom import APROM, APROMError from .dataflash import DataFlash, DataFlashError +from .logo import Logo, LogoConversionError diff --git a/evic/cli.py b/evic/cli.py index 7eeec54..8a726f6 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -233,6 +233,63 @@ def upload(inputfile, encrypted, dataflashfile, noverify): dev.write_aprom(aprom) +@usb.command('upload-logo') +@click.argument('inputfile', type=click.File('rb')) +@click.option('--invert', '-i', is_flag=True, + help='Invert the colors used in the image.') +@click.option('--no-verify', 'noverify', is_flag=True, + help='Disable data flash verification.') +def uploadlogo(inputfile, invert, noverify): + """Upload a logo to the device.""" + + dev = evic.HIDTransfer() + + # Connect the device + connect(dev) + + # Print the USB info of the device + print_usb_info(dev) + + # Read the data flash + dataflash = read_dataflash(dev, noverify) + dataflash_original = copy.deepcopy(dataflash) + + # Print the device information + print_device_info(dev, dataflash) + + # Convert the image + try: + logo = evic.logo.fromimage(inputfile, invert) + except evic.LogoConversionError as error: + print(error.message) + + # We want to boot to LDROM on restart + if not dev.ldrom: + dataflash.bootflag = 1 + + # Write data flash to the device + with handle_exceptions(IOError): + if dataflash.array != dataflash_original.array: + click.echo("Writing data flash...", nl=False) + sleep(0.1) + dev.write_dataflash(dataflash) + click.secho("OK", fg='green', bold=True) + + # We should only restart if we're not in LDROM + if not dev.ldrom: + # Restart + click.echo("Restarting the device...", nl=False) + dev.reset() + sleep(2) + click.secho("OK", fg='green', nl=False, bold=True) + # Reconnect + connect(dev) + + # Write logo to the device + click.echo("Writing logo...", nl=False) + dev.write_logo(logo) + + @usb.command('dump-dataflash') @click.option('--output', '-o', type=click.File('wb')) @click.option('--no-verify', 'noverify', is_flag=True, diff --git a/evic/device.py b/evic/device.py index 34f41e2..e04c516 100644 --- a/evic/device.py +++ b/evic/device.py @@ -259,3 +259,19 @@ def write_aprom(self, aprom): self.write(write_aprom) self.write(aprom.data) + + def write_logo(self, logo): + """Writes the logo to the the device. + + Args: + logo: A Logo object. + """ + + start = 102400 + end = len(logo.array) + + # Send the command for writing the logo + write_aprom = self.hidcmd(0xC3, start, end) + self.write(write_aprom) + + self.write(logo.array) diff --git a/evic/logo.py b/evic/logo.py new file mode 100644 index 0000000..a0ded07 --- /dev/null +++ b/evic/logo.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +""" +Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. +Copyright © Jussi Timperi + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +import binstruct +from bitarray import bitarray +from PIL import Image + + +class LogoConversionError(Exception): + """Logo conversion error.""" + + pass + + +class Logo(binstruct.StructTemplate): + """Logo class. + + Attributes: + width: Logo width (integer). + height: Logo height (integer). + """ + + width = binstruct.Int8Field(0) + height = binstruct.Int8Field(1) + + +def fromimage(image, invert=False): + """Converts an image file to a Logo object. + + Args: + image: The image that will be converted (file). + invert: True will invert colors from the source image (boolean). + + Returns: + An instance of Logo class containing the converted image. + """ + + img = Image.open(image) + + if img.size != (64, 40): + raise LogoConversionError("Image dimensions must be 64x40.") + + width, height = img.size + + # Convert to b/w + if img.mode != '1': + img = img.convert('L') + img = img.point(lambda x: 0 if x < 32 else 255, '1') + + # 1 bit per pixel + bits = bitarray(list(img.getdata())) + + # Convert to column-major order, 1 bit per pixel + img_transposed = img.transpose(Image.TRANSPOSE) + transposedbits = bitarray(list(img_transposed.getdata())) + + # Invert colors + if invert: + bits.invert() + transposedbits.invert() + + # Convert the bitarrays to bytes + imgbytes = bits.tobytes() if hasattr( + bits, 'tobytes') else bits.tostring() + transposedbytes = transposedbits.tobytes() if hasattr( + transposedbits, 'tobytes') else transposedbits.tostring() + + # Create a buffer for the logo + buff = bytearray(1024) + buff[0], buff[512] = (width,)*2 + buff[1], buff[513] = (height,)*2 + + # Copy logo to the buffer + buff[2:len(imgbytes) + 2] = imgbytes + buff[514:len(transposedbytes) + 514] = transposedbytes + + return Logo(buff, 0) diff --git a/setup.py b/setup.py index 66a0bb7..8c14cab 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,7 @@ REQUIREMENTS = [ 'binstruct', + 'bitarray', 'click' ] From 8a2245eaf4dfa05fb3f0ef5a6e32335d0e4f70a3 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Fri, 22 Apr 2016 15:17:37 +0300 Subject: [PATCH 49/69] 3.02 now tested --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 74c5f23..e6f8b89 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ Supported devices Tested firmware versions ----------------------------- -* Evic VTC Mini <=3.01 +* Evic VTC Mini <=3.02 * Presa TC75W 1.02\* * Examples from `evic-sdk `_ From cb37c44df6870e5495e88ae571d51ec78c38514b Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Fri, 22 Apr 2016 15:21:50 +0300 Subject: [PATCH 50/69] Typo fix --- evic/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evic/device.py b/evic/device.py index e04c516..57fd51f 100644 --- a/evic/device.py +++ b/evic/device.py @@ -245,7 +245,7 @@ def reset(self): self.write(reset) def write_aprom(self, aprom): - """Writes the APROM to the the device. + """Writes the APROM to the device. Args: aprom: A BinFile object containing an unencrypted APROM image. From 67c235816f2ed4c7eca766c7d2e1c6392e36f6f7 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Fri, 22 Apr 2016 15:27:55 +0300 Subject: [PATCH 51/69] Add pillow to requirements --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8c14cab..acff250 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,8 @@ REQUIREMENTS = [ 'binstruct', 'bitarray', - 'click' + 'click', + 'pillow' ] setup( From f60579706a92f7fe8e77b916319f6bc7e5500a16 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Sun, 24 Apr 2016 15:58:59 +0300 Subject: [PATCH 52/69] Refactorization --- evic/device.py | 58 ++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/evic/device.py b/evic/device.py index 57fd51f..a0e3324 100644 --- a/evic/device.py +++ b/evic/device.py @@ -116,6 +116,18 @@ def connect(self): self.product = self.device.get_product_string() self.serial = self.device.get_serial_number_string() + def send_command(self, cmd, arg1, arg2): + """Sends a HID command to the device. + + Args: + cmd: Byte long HID command + arg1: First argument to the command (integer) + arg2: Second argument to the command (integer) + """ + + command = self.hidcmd(cmd, arg1, arg2) + self.write(command) + def read_dataflash(self): """Reads the device data flash. @@ -129,8 +141,7 @@ def read_dataflash(self): end = 2048 # Send the command for reading the data flash - read_df = self.hidcmd(0x35, start, end) - self.write(read_df) + self.send_command(0x35, start, end) # Read the dataflash buf = self.read(end) @@ -138,7 +149,7 @@ def read_dataflash(self): # Something is wrong, try re-reading if dataflash.unknown1 or not dataflash.fw_version: - self.write(read_df) + self.send_command(0x35, start, end) buf = self.read(end) dataflash = DataFlash(buf[4:], 0) @@ -220,8 +231,7 @@ def write_dataflash(self, dataflash): end = 2048 # Send the command for writing the data flash - write_df = self.hidcmd(0x53, start, end) - self.write(write_df) + self.send_command(0x53, start, end) # Add checksum of the data in front of it buf = bytearray(struct.pack("=I", sum(dataflash.array))) + \ @@ -235,14 +245,26 @@ def reset_dataflash(self): Sends a data flash reset request to the firmware. """ - reset_df = self.hidcmd(0x7C, 0, 0) - self.write(reset_df) + self.send_command(0x7C, 0, 0) def reset(self): """Sends the HID command for resetting the system (0xB4)""" - reset = self.hidcmd(0xB4, 0, 0) - self.write(reset) + self.send_command(0xB4, 0, 0) + + def write_flash(self, data, start): + """Writes data to the flash memory. + + Args: + start: Start address. + """ + + end = len(data) + + # Send the command for writing the data + self.send_command(0xC3, start, end) + + self.write(data) def write_aprom(self, aprom): """Writes the APROM to the device. @@ -251,14 +273,7 @@ def write_aprom(self, aprom): aprom: A BinFile object containing an unencrypted APROM image. """ - start = 0 - end = len(aprom.data) - - # Send the command for writing the APROM - write_aprom = self.hidcmd(0xC3, start, end) - self.write(write_aprom) - - self.write(aprom.data) + self.write_flash(aprom.data, 0) def write_logo(self, logo): """Writes the logo to the the device. @@ -267,11 +282,4 @@ def write_logo(self, logo): logo: A Logo object. """ - start = 102400 - end = len(logo.array) - - # Send the command for writing the logo - write_aprom = self.hidcmd(0xC3, start, end) - self.write(write_aprom) - - self.write(logo.array) + self.write_flash(logo.array, 102400) From fe449898d0dd6ad13d7868e2c88b26dc1d96a555 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Sun, 24 Apr 2016 18:58:41 +0300 Subject: [PATCH 53/69] Require output file options. #12 --- evic/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evic/cli.py b/evic/cli.py index 8a726f6..bf5fdd5 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -291,7 +291,7 @@ def uploadlogo(inputfile, invert, noverify): @usb.command('dump-dataflash') -@click.option('--output', '-o', type=click.File('wb')) +@click.option('--output', '-o', type=click.File('wb'), required=True) @click.option('--no-verify', 'noverify', is_flag=True, help='Disable verification.') def dumpdataflash(output, noverify): @@ -344,7 +344,7 @@ def main(): @main.command() @click.argument('inputfile', type=click.File('rb')) -@click.option('--output', '-o', type=click.File('wb')) +@click.option('--output', '-o', type=click.File('wb'), required=True) def convert(inputfile, output): """Decrypt/encrypt an APROM image.""" From a392febf0168c365dece32255b42737f6b25791e Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Sun, 24 Apr 2016 19:03:41 +0300 Subject: [PATCH 54/69] Require pillow version >= 2.7.0. #12 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index acff250..f38988e 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ 'binstruct', 'bitarray', 'click', - 'pillow' + 'pillow>=2.7.0' ] setup( From cf215cfb05c02c8a6bf876c977ee45ef0434314d Mon Sep 17 00:00:00 2001 From: ReservedField Date: Sun, 24 Apr 2016 18:30:04 +0200 Subject: [PATCH 55/69] Fix logo on SSD1306 --- evic/logo.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/evic/logo.py b/evic/logo.py index a0ded07..c218d4d 100644 --- a/evic/logo.py +++ b/evic/logo.py @@ -66,20 +66,23 @@ def fromimage(image, invert=False): # 1 bit per pixel bits = bitarray(list(img.getdata())) - # Convert to column-major order, 1 bit per pixel - img_transposed = img.transpose(Image.TRANSPOSE) - transposedbits = bitarray(list(img_transposed.getdata())) + # Convert to paged column-major order + # 1 bit per pixel, 8 rows per page, LSB topmost + imgstrips = [img.crop((0, y, 64, y + 8)) for y in range(0, 40, 8)] + pagedbits = bitarray(endian='little') + for strip in imgstrips: + pagedbits += list(strip.transpose(Image.TRANSPOSE).getdata()) # Invert colors if invert: bits.invert() - transposedbits.invert() + pagedbits.invert() # Convert the bitarrays to bytes imgbytes = bits.tobytes() if hasattr( bits, 'tobytes') else bits.tostring() - transposedbytes = transposedbits.tobytes() if hasattr( - transposedbits, 'tobytes') else transposedbits.tostring() + pagedbytes = pagedbits.tobytes() if hasattr( + pagedbits, 'tobytes') else pagedbits.tostring() # Create a buffer for the logo buff = bytearray(1024) @@ -88,6 +91,6 @@ def fromimage(image, invert=False): # Copy logo to the buffer buff[2:len(imgbytes) + 2] = imgbytes - buff[514:len(transposedbytes) + 514] = transposedbytes + buff[514:len(pagedbytes) + 514] = pagedbytes return Logo(buff, 0) From c706334005bd12f88d1300f1e2dd606fe102f88e Mon Sep 17 00:00:00 2001 From: ReservedField Date: Sun, 24 Apr 2016 23:33:58 +0200 Subject: [PATCH 56/69] Rework SSD1306 logo conversion, remove pillow >= 2.7.0 requirement --- evic/logo.py | 8 +++++--- setup.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/evic/logo.py b/evic/logo.py index c218d4d..b458287 100644 --- a/evic/logo.py +++ b/evic/logo.py @@ -68,10 +68,12 @@ def fromimage(image, invert=False): # Convert to paged column-major order # 1 bit per pixel, 8 rows per page, LSB topmost - imgstrips = [img.crop((0, y, 64, y + 8)) for y in range(0, 40, 8)] + imgpixels = img.load() pagedbits = bitarray(endian='little') - for strip in imgstrips: - pagedbits += list(strip.transpose(Image.TRANSPOSE).getdata()) + for page in range(0, 5): + for x in range(0, 64): + for y in range(0, 8): + pagedbits.append(imgpixels[x, page*8 + y]) # Invert colors if invert: diff --git a/setup.py b/setup.py index f38988e..acff250 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ 'binstruct', 'bitarray', 'click', - 'pillow>=2.7.0' + 'pillow' ] setup( From ec0f3af16c53da7c548308737a69b3ca41aa4ae5 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 25 Apr 2016 01:32:48 +0300 Subject: [PATCH 57/69] Add iStick Pico to the supported devices --- README.rst | 1 + evic/device.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/README.rst b/README.rst index e6f8b89..0fe5d86 100644 --- a/README.rst +++ b/README.rst @@ -13,6 +13,7 @@ Supported devices * Evic VTC Mini * Cuboid * iStick TC100W* +* iStick Pico* * Presa TC75W* * Vaporflask Classic* * Vaporflask Lite* diff --git a/evic/device.py b/evic/device.py index a0e3324..054b1c5 100644 --- a/evic/device.py +++ b/evic/device.py @@ -53,6 +53,7 @@ class HIDTransfer(object): product_names = {'E052': "eVic-VTC Mini", 'E060': "Cuboid", 'M011': "iStick TC100W", + 'M041': "iStick Pico", 'W007': "Presa TC75W", 'W010': "Classic", 'W011': "Lite", @@ -61,6 +62,7 @@ class HIDTransfer(object): supported_product_ids = {'E052': ['E052', 'W007'], 'E060': ['E060'], 'M011': ['M011'], + 'M041': ['M041'], 'W007': ['W007', 'E052'], 'W010': ['W010'], 'W011': ['W011'], From d94dbed0441a6206f215a06b2558e4c410706bb8 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 25 Apr 2016 15:03:07 +0300 Subject: [PATCH 58/69] Add Cuboid Mini to the supported devices --- README.rst | 1 + evic/device.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 0fe5d86..25d0036 100644 --- a/README.rst +++ b/README.rst @@ -11,6 +11,7 @@ Supported devices --------------------- * Evic VTC Mini +* Cuboid Mini * Cuboid * iStick TC100W* * iStick Pico* diff --git a/evic/device.py b/evic/device.py index 054b1c5..5307f27 100644 --- a/evic/device.py +++ b/evic/device.py @@ -51,6 +51,7 @@ class HIDTransfer(object): vid = 0x0416 pid = 0x5020 product_names = {'E052': "eVic-VTC Mini", + 'E056': "CUBOID MINI", 'E060': "Cuboid", 'M011': "iStick TC100W", 'M041': "iStick Pico", @@ -60,6 +61,7 @@ class HIDTransfer(object): 'W013': "Stout", 'W014': "Reuleaux RX200"} supported_product_ids = {'E052': ['E052', 'W007'], + 'E056': ['E056'], 'E060': ['E060'], 'M011': ['M011'], 'M041': ['M041'], From fdc16f51a6f1f2c2892cff97359964a6eeab0da8 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Sun, 8 May 2016 21:22:37 +0300 Subject: [PATCH 59/69] Correct the LDROM detection --- evic/dataflash.py | 5 ++--- evic/device.py | 8 +------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/evic/dataflash.py b/evic/dataflash.py index 1093128..eb7b78b 100644 --- a/evic/dataflash.py +++ b/evic/dataflash.py @@ -45,8 +45,7 @@ class DataFlash(binstruct.StructTemplate): bootflag = binstruct.Int8Field(9) product_id = binstruct.StringField(312, 4) fw_version = binstruct.Int32Field(256) - unknown1 = binstruct.Int32Field(260) - unknown2 = binstruct.Int32Field(264) + ldrom_version = binstruct.Int32Field(260) def verify(self, checksum): """Verifies the data flash against given checksum. @@ -58,5 +57,5 @@ def verify(self, checksum): DataFlashError: Data flash verification failed. """ - if sum(self.array) != checksum or not checksum | self.unknown2: + if sum(self.array) != checksum: raise DataFlashError("Data flash verification failed.") diff --git a/evic/device.py b/evic/device.py index 5307f27..8f316e2 100644 --- a/evic/device.py +++ b/evic/device.py @@ -151,17 +151,11 @@ def read_dataflash(self): buf = self.read(end) dataflash = DataFlash(buf[4:], 0) - # Something is wrong, try re-reading - if dataflash.unknown1 or not dataflash.fw_version: - self.send_command(0x35, start, end) - buf = self.read(end) - dataflash = DataFlash(buf[4:], 0) - # Get the checksum from the beginning of the data flash transfer checksum = struct.unpack('=I', bytes(buf[0:4]))[0] # Are we booted to LDROM? - self.ldrom = dataflash.fw_version == 0 + self.ldrom = dataflash.ldrom_version or not dataflash.fw_version return (dataflash, checksum) From 7c42e4eee93da8ada0871fe840f7bb1cb08da286 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Mon, 9 May 2016 00:27:26 +0300 Subject: [PATCH 60/69] Update documentation --- evic/dataflash.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/evic/dataflash.py b/evic/dataflash.py index eb7b78b..31abb2a 100644 --- a/evic/dataflash.py +++ b/evic/dataflash.py @@ -37,8 +37,7 @@ class DataFlash(binstruct.StructTemplate): 1 = LDROM product_id: Product ID string. fw_version: An integer firmware version number. - unknown1: TODO - unknown2: TODO + ldrom_version: An integer LDROM versrion number. """ hw_version = binstruct.Int32Field(4) From 73f9c5dc5fd3bb2f7f926157ac37324eb5bf4763 Mon Sep 17 00:00:00 2001 From: ReservedField Date: Tue, 7 Jun 2016 22:45:08 +0200 Subject: [PATCH 61/69] Fix logo upload for all supported devices The Logo class now allows for arbitrary resolutions, as long as the dimensions are multiples of 8 and don't exceed the buffer capacity. Before uploading it is checked that the device actually supports logos and that the resolution is compatible with the device. --- evic/cli.py | 14 +++++++++++--- evic/device.py | 6 ++++++ evic/logo.py | 12 +++++++----- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/evic/cli.py b/evic/cli.py index bf5fdd5..2ba055d 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -258,10 +258,18 @@ def uploadlogo(inputfile, invert, noverify): print_device_info(dev, dataflash) # Convert the image - try: + with handle_exceptions(evic.LogoConversionError): + click.echo("Converting logo...", nl=False) + # Check supported logo size + try: + logosize = dev.supported_logo_size[dataflash.product_id] + except KeyError: + raise evic.LogoConversionError("Device doesn't support logos.") + # Perform the actual conversion logo = evic.logo.fromimage(inputfile, invert) - except evic.LogoConversionError as error: - print(error.message) + if (logo.width, logo.height) != logosize: + raise evic.LogoConversionError("Device only supports {}x{} logos." + .format(*logosize)) # We want to boot to LDROM on restart if not dev.ldrom: diff --git a/evic/device.py b/evic/device.py index 8f316e2..0afd3af 100644 --- a/evic/device.py +++ b/evic/device.py @@ -39,6 +39,8 @@ class HIDTransfer(object): supported_product_ids: A dictionary mapping product ID to a list of strings containing the IDs of the products with compatible firmware. + supported_logo_size: A dictionary mapping product ID to the allowed + logo resolution as a (width, height) tuple. hid_signature: A bytearray containing the HID command signature (4 bytes). device: A HIDAPI device. @@ -70,6 +72,10 @@ class HIDTransfer(object): 'W011': ['W011'], 'W013': ['W013'], 'W014': ['W014']} + supported_logo_size = {'E052': (64, 40), + 'E056': (64, 40), + 'E060': (64, 40), + 'M041': (96, 16)} # 0x43444948 hid_signature = bytearray(b'HIDC') diff --git a/evic/logo.py b/evic/logo.py index b458287..1bf6996 100644 --- a/evic/logo.py +++ b/evic/logo.py @@ -53,11 +53,13 @@ def fromimage(image, invert=False): img = Image.open(image) - if img.size != (64, 40): - raise LogoConversionError("Image dimensions must be 64x40.") - width, height = img.size + if width % 8 != 0 or height % 8 != 0: + raise LogoConversionError("Image dimensions must be multiples of 8.") + if width * height > 4080: + raise LogoConversionError("Image is too big.") + # Convert to b/w if img.mode != '1': img = img.convert('L') @@ -70,8 +72,8 @@ def fromimage(image, invert=False): # 1 bit per pixel, 8 rows per page, LSB topmost imgpixels = img.load() pagedbits = bitarray(endian='little') - for page in range(0, 5): - for x in range(0, 64): + for page in range(0, height / 8): + for x in range(0, width): for y in range(0, 8): pagedbits.append(imgpixels[x, page*8 + y]) From 497f63ff746548419bf9822e9fd316fd24968923 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Wed, 8 Jun 2016 01:28:08 +0300 Subject: [PATCH 62/69] eGrip II is supported --- README.rst | 1 + evic/device.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 25d0036..dc76d7f 100644 --- a/README.rst +++ b/README.rst @@ -13,6 +13,7 @@ Supported devices * Evic VTC Mini * Cuboid Mini * Cuboid +* eGrip II* * iStick TC100W* * iStick Pico* * Presa TC75W* diff --git a/evic/device.py b/evic/device.py index 0afd3af..ae124af 100644 --- a/evic/device.py +++ b/evic/device.py @@ -55,6 +55,7 @@ class HIDTransfer(object): product_names = {'E052': "eVic-VTC Mini", 'E056': "CUBOID MINI", 'E060': "Cuboid", + 'E083': "eGrip II", 'M011': "iStick TC100W", 'M041': "iStick Pico", 'W007': "Presa TC75W", @@ -75,6 +76,7 @@ class HIDTransfer(object): supported_logo_size = {'E052': (64, 40), 'E056': (64, 40), 'E060': (64, 40), + 'E083': (64, 40), 'M041': (96, 16)} # 0x43444948 hid_signature = bytearray(b'HIDC') From 5669e1763a932fea0d64597cbb75a569f1acbac9 Mon Sep 17 00:00:00 2001 From: "Magic.Crazy" Date: Tue, 14 Jun 2016 15:55:07 +0200 Subject: [PATCH 63/69] Fix logo conversion's division to return an integer --- evic/logo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evic/logo.py b/evic/logo.py index 1bf6996..c2a5a0b 100644 --- a/evic/logo.py +++ b/evic/logo.py @@ -72,7 +72,7 @@ def fromimage(image, invert=False): # 1 bit per pixel, 8 rows per page, LSB topmost imgpixels = img.load() pagedbits = bitarray(endian='little') - for page in range(0, height / 8): + for page in range(0, height // 8): for x in range(0, width): for y in range(0, 8): pagedbits.append(imgpixels[x, page*8 + y]) From 5c92a2ec63b192ba144d198d84f074a36535db66 Mon Sep 17 00:00:00 2001 From: Jacob Oliver Date: Mon, 27 Jun 2016 23:33:51 +0100 Subject: [PATCH 64/69] Add support for Reuleaux RX200S Update to support RX200S - Confirmed working. --- evic/device.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/evic/device.py b/evic/device.py index ae124af..7e941af 100644 --- a/evic/device.py +++ b/evic/device.py @@ -62,7 +62,8 @@ class HIDTransfer(object): 'W010': "Classic", 'W011': "Lite", 'W013': "Stout", - 'W014': "Reuleaux RX200"} + 'W014': "Reuleaux RX200", + 'W033': "Reuleaux RX200S"} supported_product_ids = {'E052': ['E052', 'W007'], 'E056': ['E056'], 'E060': ['E060'], @@ -72,7 +73,8 @@ class HIDTransfer(object): 'W010': ['W010'], 'W011': ['W011'], 'W013': ['W013'], - 'W014': ['W014']} + 'W014': ['W014'], + 'W033': ['W033']} supported_logo_size = {'E052': (64, 40), 'E056': (64, 40), 'E060': (64, 40), From ac8964bdc1599604c19cbeb172228ee99cc0c959 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Sun, 14 Aug 2016 01:35:18 +0300 Subject: [PATCH 65/69] Refactor device info into a tuple --- evic/cli.py | 51 ++++++++++++++++++++++++++++++------------------ evic/device.py | 53 +++++++++++++++++--------------------------------- 2 files changed, 50 insertions(+), 54 deletions(-) diff --git a/evic/cli.py b/evic/cli.py index 2ba055d..9d81038 100644 --- a/evic/cli.py +++ b/evic/cli.py @@ -29,6 +29,7 @@ import evic +from .device import DeviceInfo @contextmanager def handle_exceptions(*exceptions): @@ -107,21 +108,17 @@ def read_dataflash(dev, verify): return dataflash -def print_device_info(dev, dataflash): +def print_device_info(device_info, dataflash): """Prints the device information found from data flash. Args: - dev: evic.HIDTransfer object. + device_info: device.DeviceInfo tuple. dataflash: evic.DataFlash object. """ - # Find the product name - product_name = dev.product_names.get(dataflash.product_id, - "Unknown device") - # Print out the information click.echo("\tDevice name: ", nl=False) - click.secho(product_name, bold=True) + click.secho(device_info.name, bold=True) click.echo("\tFirmware version: ", nl=False) click.secho("{0:.2f}".format(dataflash.fw_version / 100.0), bold=True) click.echo("\tHardware version: ", nl=False) @@ -170,8 +167,12 @@ def upload(inputfile, encrypted, dataflashfile, noverify): dataflash = read_dataflash(dev, verify) dataflash_original = copy.deepcopy(dataflash) + # Get the device info + device_info = dev.devices.get(dataflash.product_id, + DeviceInfo("Unknown device", None, None)) + # Print the device information - print_device_info(dev, dataflash) + print_device_info(device_info, dataflash) # Read the APROM image aprom = evic.APROM(inputfile.read()) @@ -182,9 +183,12 @@ def upload(inputfile, encrypted, dataflashfile, noverify): if 'aprom' not in noverify: with handle_exceptions(evic.APROMError): click.echo("Verifying APROM...", nl=False) - aprom.verify( - dev.supported_product_ids[dataflash.product_id], - dataflash.hw_version) + + supported_product_ids = [dataflash.product_id] + if device_info.supported_product_ids: + supported_product_ids.extend(device_info.supported_product_ids) + + aprom.verify(supported_product_ids, dataflash.hw_version) # Are we using a data flash file? if dataflashfile: @@ -254,22 +258,27 @@ def uploadlogo(inputfile, invert, noverify): dataflash = read_dataflash(dev, noverify) dataflash_original = copy.deepcopy(dataflash) + # Get the device info + device_info = dev.devices.get(dataflash.product_id, + DeviceInfo("Unknown device", None, None)) + # Print the device information - print_device_info(dev, dataflash) + print_device_info(device_info, dataflash) # Convert the image with handle_exceptions(evic.LogoConversionError): click.echo("Converting logo...", nl=False) - # Check supported logo size - try: - logosize = dev.supported_logo_size[dataflash.product_id] - except KeyError: + + # Check supported logo dimensions + logo_dimensions = device_info.logo_dimensions + if not logo_dimensions: raise evic.LogoConversionError("Device doesn't support logos.") + # Perform the actual conversion logo = evic.logo.fromimage(inputfile, invert) - if (logo.width, logo.height) != logosize: + if (logo.width, logo.height) != logo_dimensions: raise evic.LogoConversionError("Device only supports {}x{} logos." - .format(*logosize)) + .format(*logo_dimensions)) # We want to boot to LDROM on restart if not dev.ldrom: @@ -316,8 +325,12 @@ def dumpdataflash(output, noverify): # Read the data flash dataflash = read_dataflash(dev, noverify) + # Get the device info + device_info = dev.devices.get(dataflash.product_id, + DeviceInfo("Unknown device", None, None)) + # Print the device information - print_device_info(dev, dataflash) + print_device_info(device_info, dataflash) # Write the data flash to the file with handle_exceptions(IOError): diff --git a/evic/device.py b/evic/device.py index 7e941af..8bc0784 100644 --- a/evic/device.py +++ b/evic/device.py @@ -18,6 +18,7 @@ """ import struct +from collections import namedtuple try: import hid @@ -27,6 +28,8 @@ from .dataflash import DataFlash +DeviceInfo = namedtuple('DeviceInfo', + 'name supported_product_ids logo_dimensions') class HIDTransfer(object): """Generic Nuvoton HID Transfer device class. @@ -34,13 +37,7 @@ class HIDTransfer(object): Attributes: vid: USB vendor ID. pid: USB product ID. - product_names: A dictionary mapping product IDs to - product name strings. - supported_product_ids: A dictionary mapping product ID to a list of - strings containing the IDs of the products - with compatible firmware. - supported_logo_size: A dictionary mapping product ID to the allowed - logo resolution as a (width, height) tuple. + devices: A dictionary mapping product IDs to DeviceInfo tuples. hid_signature: A bytearray containing the HID command signature (4 bytes). device: A HIDAPI device. @@ -52,34 +49,20 @@ class HIDTransfer(object): vid = 0x0416 pid = 0x5020 - product_names = {'E052': "eVic-VTC Mini", - 'E056': "CUBOID MINI", - 'E060': "Cuboid", - 'E083': "eGrip II", - 'M011': "iStick TC100W", - 'M041': "iStick Pico", - 'W007': "Presa TC75W", - 'W010': "Classic", - 'W011': "Lite", - 'W013': "Stout", - 'W014': "Reuleaux RX200", - 'W033': "Reuleaux RX200S"} - supported_product_ids = {'E052': ['E052', 'W007'], - 'E056': ['E056'], - 'E060': ['E060'], - 'M011': ['M011'], - 'M041': ['M041'], - 'W007': ['W007', 'E052'], - 'W010': ['W010'], - 'W011': ['W011'], - 'W013': ['W013'], - 'W014': ['W014'], - 'W033': ['W033']} - supported_logo_size = {'E052': (64, 40), - 'E056': (64, 40), - 'E060': (64, 40), - 'E083': (64, 40), - 'M041': (96, 16)} + devices = {'E052': DeviceInfo("eVic-VTC Mini", ['W007'], (64, 40)), + 'E056': DeviceInfo("CUBOID MINI", None, (64, 40)), + 'E060': DeviceInfo("Cuboid", None, (64, 40)), + 'E083': DeviceInfo("eGrip II", None, (64, 40)), + 'M011': DeviceInfo("iStick TC100W", None, None), + 'M041': DeviceInfo("iStick Pico", None, (96, 16)), + 'W007': DeviceInfo("Presa TC75W", ['E052'], None), + 'W010': DeviceInfo("Classic", None, None), + 'W011': DeviceInfo("Lite", None, None), + 'W013': DeviceInfo("Stout", None, None), + 'W014': DeviceInfo("Reuleaux RX200", None, None), + 'W033': DeviceInfo("Reuleaux RX200S", None, None) + } + # 0x43444948 hid_signature = bytearray(b'HIDC') From 9ff07b14f095d275e6b4646a6600645c6eef0d78 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Sun, 14 Aug 2016 03:24:11 +0300 Subject: [PATCH 66/69] Add some devices --- README.rst | 15 +++++++++++++-- evic/device.py | 11 ++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index dc76d7f..8bd3c50 100644 --- a/README.rst +++ b/README.rst @@ -10,25 +10,36 @@ Evic is a USB programmer for devices based on the Joyetech Evic VTC Mini. Supported devices --------------------- +* eVic VTwo* * Evic VTC Mini * Cuboid Mini * Cuboid * eGrip II* +* eVic AIO* +* eVic VTwo mini* +* eVic Basic* * iStick TC100W* -* iStick Pico* +* ASTER* +* iStick Pico +* iStick Pico Mega* +* iPower* * Presa TC75W* * Vaporflask Classic* * Vaporflask Lite* * Vaporflask Stout* * Reuleaux RX200* +* CENTURION* +* Reuleaux RX2/3* +* Reuleaux RX200S* \*Untested Tested firmware versions ----------------------------- -* Evic VTC Mini <=3.02 +* Evic VTC Mini <=3.03 * Presa TC75W 1.02\* +* iStick Pico 1.01 * Examples from `evic-sdk `_ \*Flashing Presa firmware to a VTC Mini requires changing the hardware version diff --git a/evic/device.py b/evic/device.py index 8bc0784..0d94545 100644 --- a/evic/device.py +++ b/evic/device.py @@ -49,17 +49,26 @@ class HIDTransfer(object): vid = 0x0416 pid = 0x5020 - devices = {'E052': DeviceInfo("eVic-VTC Mini", ['W007'], (64, 40)), + devices = {'E043': DeviceInfo("eVic VTwo", None, (64, 40)), + 'E052': DeviceInfo("eVic-VTC Mini", ['W007'], (64, 40)), 'E056': DeviceInfo("CUBOID MINI", None, (64, 40)), 'E060': DeviceInfo("Cuboid", None, (64, 40)), 'E083': DeviceInfo("eGrip II", None, (64, 40)), + 'E092': DeviceInfo("eVic AIO", None, (64, 40)), + 'E115': DeviceInfo("eVic VTwo mini", None, (64, 40)), + 'E150': DeviceInfo("eVic Basic", None, (64, 40)), 'M011': DeviceInfo("iStick TC100W", None, None), + 'M037': DeviceInfo("ASTER", None, (96, 16)), 'M041': DeviceInfo("iStick Pico", None, (96, 16)), + 'M045': DeviceInfo("iStick Pico Mega", None, (96, 16)), + 'M046': DeviceInfo("iPower", None, (96, 16)), 'W007': DeviceInfo("Presa TC75W", ['E052'], None), 'W010': DeviceInfo("Classic", None, None), 'W011': DeviceInfo("Lite", None, None), 'W013': DeviceInfo("Stout", None, None), 'W014': DeviceInfo("Reuleaux RX200", None, None), + 'W016': DeviceInfo("CENTURION", None, None), + 'W018': DeviceInfo("Reuleaux RX2/3", None, (64, 48)), 'W033': DeviceInfo("Reuleaux RX200S", None, None) } From f916017f987cac6eb90bf25e521455d17d4e3440 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Sun, 14 Aug 2016 15:07:07 +0300 Subject: [PATCH 67/69] s/examples/binaries/ --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 8bd3c50..344ddbf 100644 --- a/README.rst +++ b/README.rst @@ -40,7 +40,7 @@ Tested firmware versions * Evic VTC Mini <=3.03 * Presa TC75W 1.02\* * iStick Pico 1.01 -* Examples from `evic-sdk `_ +* Binaries built with `evic-sdk `_ \*Flashing Presa firmware to a VTC Mini requires changing the hardware version on some devices. Backup your data flash before flashing! From 073f26f7b2a206374de38b5eb0d7cd1da57962cd Mon Sep 17 00:00:00 2001 From: Jan Sperling Date: Wed, 12 Oct 2016 15:19:25 +0200 Subject: [PATCH 68/69] added support for Wismec Reuleaux RX75 --- evic/device.py | 1 + 1 file changed, 1 insertion(+) diff --git a/evic/device.py b/evic/device.py index 0d94545..fce6aff 100644 --- a/evic/device.py +++ b/evic/device.py @@ -69,6 +69,7 @@ class HIDTransfer(object): 'W014': DeviceInfo("Reuleaux RX200", None, None), 'W016': DeviceInfo("CENTURION", None, None), 'W018': DeviceInfo("Reuleaux RX2/3", None, (64, 48)), + 'W026': DeviceInfo("Reuleaux RX75", None, (64, 48)), 'W033': DeviceInfo("Reuleaux RX200S", None, None) } From 8e44854a0c47c5e1ef08719eb5cf9184eb2042aa Mon Sep 17 00:00:00 2001 From: Christophe Le Roy Date: Sat, 15 Oct 2016 22:53:49 +0200 Subject: [PATCH 69/69] Add eVic VTC Dual Firmware push to device is OK, other functions need testing. --- README.rst | 1 + evic/device.py | 1 + 2 files changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 344ddbf..3e426d1 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,7 @@ Supported devices * Evic VTC Mini * Cuboid Mini * Cuboid +* eVic VTC Dual* * eGrip II* * eVic AIO* * eVic VTwo mini* diff --git a/evic/device.py b/evic/device.py index 0d94545..d0ea0f9 100644 --- a/evic/device.py +++ b/evic/device.py @@ -53,6 +53,7 @@ class HIDTransfer(object): 'E052': DeviceInfo("eVic-VTC Mini", ['W007'], (64, 40)), 'E056': DeviceInfo("CUBOID MINI", None, (64, 40)), 'E060': DeviceInfo("Cuboid", None, (64, 40)), + 'E079': DeviceInfo("eVic VTC Dual", None, (64, 40)), 'E083': DeviceInfo("eGrip II", None, (64, 40)), 'E092': DeviceInfo("eVic AIO", None, (64, 40)), 'E115': DeviceInfo("eVic VTwo mini", None, (64, 40)),