# coding: utf-8

import ivm
import time
import matplotlib.pyplot as plt
import traceback
import sys

NUM_POINTS_IN_CURVE = 100


def open_device():
    """
    Ask user for a serial port number
    and try to open device on this port
    """

    print("Enter serial port.")
    print("Format for Windows: com:\\\\.\\COM29")
    print("Format for Linux: /dev/ttyACM0")
    serial_port_url = input()

    try:
        device = ivm.IvmDeviceHandle(serial_port_url)
    except RuntimeError:
        print("Cannot open device.")
        print("Please check serial port URL and try again.")
        sys.exit(-1)

    return device


def print_device_info(device):
    """
    Read general product information
    from the connected device
    """
    print("Read device information... ", end="")

    try:
        device_info = device.get_identity_information()
    except Exception as e:
        print(e)
        traceback.print_exc()
        sys.exit(-1)

    print("Done")
    print("  -- Device information --")
    print("  Product: {} {}".format(bytes(device_info.manufacturer).decode("utf-8"),
                                    bytes(device_info.product_name).decode("utf-8")))
    print("  Hardware version: {}.{}.{}".format(device_info.hardware_major,
                                                device_info.hardware_minor,
                                                device_info.hardware_bugfix))
    print("  Serial number: {}".format(device_info.serial_number))
    print("  Firmware version: {}.{}.{}".format(device_info.firmware_major,
                                                device_info.firmware_minor,
                                                device_info.firmware_bugfix))


def calibrate_device(device):
    """
    Run device automatic calibration
    Normally should be done only once
    after device power on before all measurements
    """
    print("Run device automatic calibration... ", end="")

    try:
        calibration_out = device.start_autocalibration()
    except Exception as e:
        print(e)
        traceback.print_exc()
        sys.exit(-1)

    print("Done\n")

    r = ivm.IvmDeviceHandle.StartAutocalibrationResponse.Result()
    if calibration_out.result == r.CALIBRATION_OK:
        print("  Device calibrated successfully")
    else:
        print("  There were some errors during calibration")


def setup_device(device):
    """
    Setup device for measurements
    Settings will be applied for all following measurements
    until the next reconfiguration or device power down or reset.
    """
    print("Setup device... ", end="")

    # 1. Read settings and modify them
    try:
        measurement_settings = device.get_measurement_settings()
    except Exception as e:
        print(e)
        traceback.print_exc()
        sys.exit(-1)

    # For current sensor mode constants
    csm = ivm.IvmDeviceHandle.MeasurementSettingsRequest.CurrentSensorMode()
    om = ivm.IvmDeviceHandle.MeasurementSettingsRequest.OutputMode()

    # Probe signal frequency in Hz
    measurement_settings.probe_signal_frequency = 100
    # We want to get only one period (Resolution: NUM_POINTS_IN_CURVE / period)
    measurement_settings.number_points = NUM_POINTS_IN_CURVE
    measurement_settings.sampling_rate = (measurement_settings.probe_signal_frequency *
                                          NUM_POINTS_IN_CURVE)
    measurement_settings.number_chargePoints = 0                             # No precharge
    measurement_settings.max_voltage = 12                                    # This is open circuit voltage. Real voltage will be smaller.
    measurement_settings.current_sensor_mode = csm.CURRENT_SENSE_MODE_I_MID  # Medium currents
    measurement_settings.output_mode = om.OUT_MODE_PROBE_SIGNAL_CONTINUOUS

    # 2. Apply settings
    try:
        device.set_measurement_settings(measurement_settings)
    except Exception as e:
        print(e)
        traceback.print_exc()
        sys.exit(-1)

    print("Done")
    return measurement_settings


def make_measurement(device):
    """
    Launch measuremnt,
    wait until the measurement will be finished,
    read measurement results from the device
    """
    print("Make measuremnet... ", end="")

    # 1. Launch measuremnt.
    try:
        device.start_measurement()
    except Exception as e:
        print(e)
        traceback.print_exc()
        sys.exit(-1)

    # 2. Wait until the measurement will be finished
    # start_measurement() is non-blocking
    # check, that the measurement is finished,
    # before reading results is required
    measurement_status = device.CheckMeasurementStatusResponse()
    try:
        measurement_status = device.check_measurement_status()
    except Exception as e:
        print(e)
        traceback.print_exc()
        sys.exit(-1)

    rs = ivm.IvmDeviceHandle.CheckMeasurementStatusResponse.ReadyStatus()

    while measurement_status.ready_status != rs.MEASUREMENT_COMPLETE:
        time.sleep(0.1)  # Small delay
        try:
            measurement_status = device.check_measurement_status()
        except Exception as e:
            print(e)
            traceback.print_exc()
            sys.exit(-1)
        measurement_status = device.check_measurement_status()

    # 3. Read measurement results from the device
    FRAME_SIZE = ivm.IvmDeviceHandle.GetMeasurementRequest.FrameNumber().FRAME_SIZE
    voltages = []
    currents = []

    # Read full frames
    for frame_number in range(NUM_POINTS_IN_CURVE // FRAME_SIZE):
        try:
            frame = device.get_measurement(frame_number)
        except Exception as e:
            print(e)
            traceback.print_exc()
            sys.exit(-1)

        currents.extend(list(frame.current))
        voltages.extend(list(frame.voltage))

    # Read residual frame
    if NUM_POINTS_IN_CURVE % FRAME_SIZE > 0:
        frame_number = NUM_POINTS_IN_CURVE // FRAME_SIZE
        try:
            frame = device.get_measurement(frame_number)
        except Exception as e:
            print(e)
            traceback.print_exc()
            sys.exit(-1)

        currents.extend(list(frame.current[:NUM_POINTS_IN_CURVE % FRAME_SIZE]))
        voltages.extend(list(frame.voltage[:NUM_POINTS_IN_CURVE % FRAME_SIZE]))

    print("Done")

    return (voltages, currents)


def plot_measurement(voltages, currents, settings):
    """
    Make a plot of the measured IV-curve
    """
    padding_factor = 1.2  # To provide small frame around the curve (just for better view)
    max_voltage = settings.max_voltage * padding_factor

    # For current sensor mode constants
    csm = ivm.IvmDeviceHandle.MeasurementSettingsRequest.CurrentSensorMode()

    if settings.current_sensor_mode == csm.CURRENT_SENSE_MODE_I_LOW:
        # Current sense resistance ~ 47.5 kOhm
        max_current = settings.max_voltage / 47.5 * padding_factor
    elif settings.current_sensor_mode == csm.CURRENT_SENSE_MODE_I_MID:
        # Current sense resistance ~ 4.75 kOhm
        max_current = settings.max_voltage / 4.75 * padding_factor
    else:
        # Current sense resistance ~ 475 Ohm
        max_current = settings.max_voltage / 0.475 * padding_factor

    colors = plt.rcParams['axes.prop_cycle'].by_key()['color']

    plt.figure(figsize=(15, 5))
    plt.subplot(121)
    plt.title("Временные зависимости")
    plt.plot(voltages, marker="o", linestyle="", label="Напряжение, В", color=colors[0])
    plt.xlabel("Номер точки")
    plt.ylabel("Напряжение, В")
    plt.ylim(-max_voltage, max_voltage)
    plt.tick_params(axis='y', colors=colors[0])
    plt.legend

    plt.twinx()
    plt.plot(currents, marker="s", linestyle="", label="Ток, мА", color=colors[1])
    plt.ylabel("Ток, мА")
    plt.ylim(-max_current, max_current)
    plt.tick_params(axis='y', colors=colors[1])
    plt.legend()

    plt.subplot(122)
    plt.title("ВАХ")
    plt.plot(voltages, currents, marker="o", linestyle="")
    plt.ylabel("Ток, мА")
    plt.xlabel("Напряжение, В")
    plt.xlim(-max_voltage, max_voltage)
    plt.ylim(-max_current, max_current)
    plt.grid()
    plt.show()


if __name__ == "__main__":
    print("EyePoint IVM application example")
    print("--------------------------------")

    device = open_device()

    if device is not None:
        print_device_info(device)
        calibrate_device(device)
        settings = setup_device(device)
        voltages, currents = make_measurement(device)
        device.close_device()
        plot_measurement(voltages, currents, settings)