#!/usr/bin/env python2
# Tool to graph temperature sensor ADC resolution
#
# Copyright (C) 2020  Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, os, optparse
import matplotlib


######################################################################
# Dummy config / printer / etc. class emulation
######################################################################

class DummyConfig:
    def __init__(self, config_settings):
        self.config_settings = config_settings
        self.sensor_factories = {}
    # Emulate config class
    def getfloat(self, option, default, **kw):
        return self.config_settings.get(option, default)
    def get(self, option, default=None):
        return default
    def get_printer(self):
        return self
    def get_name(self):
        return "dummy"
    # Emulate printer class
    def load_object(self, config, name):
        return self
    def lookup_object(self, name):
        return self
    # Emulate heaters class
    def add_sensor_factory(self, name, factory):
        self.sensor_factories[name] = factory
    def do_create_sensor(self, sensor_type):
        return self.sensor_factories[sensor_type](self).adc_convert
    # Emulate query_adc class
    def register_adc(self, name, klass):
        pass
    # Emulate pins class
    def setup_pin(self, pin_type, pin_name):
        return self
    # Emulate mcu_adc class
    def setup_adc_callback(self, time, callback):
        pass


######################################################################
# Plotting
######################################################################

def plot_adc_resolution(config, sensors):
    # Temperature list
    all_temps = [float(i) for i in range(1, 351)]
    temps = all_temps[:-1]
    # Build plot
    fig, (ax1, ax2) = matplotlib.pyplot.subplots(nrows=2, sharex=True)
    pullup = config.getfloat('pullup_resistor', 0.)
    adc_voltage = config.getfloat('adc_voltage', 0.)
    ax1.set_title("Temperature Sensor (pullup=%.0f, adc_voltage=%.3f)"
                  % (pullup, adc_voltage))
    ax1.set_ylabel('ADC')
    ax2.set_ylabel('ADC change per 1C')
    for sensor in sensors:
        sc = config.do_create_sensor(sensor)
        adcs = [sc.calc_adc(t) for t in all_temps]
        ax1.plot(temps, adcs[:-1], label=sensor, alpha=0.6)
        adc_deltas = [abs(adcs[i+1] - adcs[i]) for i in range(len(temps))]
        ax2.plot(temps, adc_deltas, alpha=0.6)
    fontP = matplotlib.font_manager.FontProperties()
    fontP.set_size('x-small')
    ax1.legend(loc='best', prop=fontP)
    ax2.set_xlabel('Temperature (C)')
    ax1.grid(True)
    ax2.grid(True)
    fig.tight_layout()
    return fig

def plot_resistance(config, sensors):
    # Temperature list
    all_temps = [float(i) for i in range(1, 351)]
    # Build plot
    fig, ax = matplotlib.pyplot.subplots()
    pullup = config.getfloat('pullup_resistor', 0.)
    ax.set_title("Temperature Sensor (pullup=%.0f)" % (pullup,))
    ax.set_ylabel('Resistance (Ohms)')
    for sensor in sensors:
        sc = config.do_create_sensor(sensor)
        adcs = [sc.calc_adc(t) for t in all_temps]
        rs = [pullup * adc / (1.0 - adc) for adc in adcs]
        ax.plot(all_temps, rs, label=sensor, alpha=0.6)
    fontP = matplotlib.font_manager.FontProperties()
    fontP.set_size('x-small')
    ax.legend(loc='best', prop=fontP)
    ax.set_xlabel('Temperature (C)')
    ax.grid(True)
    fig.tight_layout()
    return fig


######################################################################
# Startup
######################################################################

def setup_matplotlib(output_to_file):
    global matplotlib
    if output_to_file:
        matplotlib.rcParams.update({'figure.autolayout': True})
        matplotlib.use('Agg')
    import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
    import matplotlib.ticker

def import_sensors(config):
    global extras
    # Load adc_temperature.py and thermistor.py modules
    kdir = os.path.join(os.path.dirname(__file__), '..', 'klippy')
    sys.path.append(kdir)
    import extras.adc_temperature, extras.thermistor
    extras.thermistor.load_config(config)
    extras.adc_temperature.load_config(config)

def main():
    # Parse command-line arguments
    usage = "%prog [options]"
    opts = optparse.OptionParser(usage)
    opts.add_option("-o", "--output", type="string", dest="output",
                    default=None, help="filename of output graph")
    opts.add_option("-p", "--pullup", type="float", dest="pullup",
                    default=4700., help="pullup resistor")
    opts.add_option("-v", "--voltage", type="float", dest="voltage",
                    default=5., help="pullup resistor")
    opts.add_option("-s", "--sensors", type="string", dest="sensors",
                    default="", help="list of sensors (comma separated)")
    opts.add_option("-r", "--resistance", action="store_true",
                    help="graph sensor resistance")
    options, args = opts.parse_args()
    if len(args) != 0:
        opts.error("Incorrect number of arguments")

    # Import sensors
    config_settings = {'pullup_resistor': options.pullup,
                       'adc_voltage': options.voltage}
    config = DummyConfig(config_settings)
    import_sensors(config)

    # Determine sensors to graph
    if options.sensors:
        sensors = [s.strip() for s in options.sensors.split(',')]
    else:
        sensors = sorted(config.sensor_factories.keys())

    # Draw graph
    setup_matplotlib(options.output is not None)
    if options.resistance:
        fig = plot_resistance(config, sensors)
    else:
        fig = plot_adc_resolution(config, sensors)

    # Show graph
    if options.output is None:
        matplotlib.pyplot.show()
    else:
        fig.set_size_inches(8, 6)
        fig.savefig(options.output)

if __name__ == '__main__':
    main()
