Graphing a remote head's temperature over time

This Python example, remote_head_temperature_graph.py, graphs the temperature of a remote head over time. Supported remote head modules are the N1045A electrical and N1055A TDR module. A graph is saved as a graphics file (PNG) and as a postscript file (PS, vector):

  • remote_head_temperatures.png
  • remote_head_temperatures.ps

The postscript file is editable in Adobe Illustrator as it is a vector file. The following picture shows the temperature variation of a remote head in a thermally uncontrolled setting. A reading was taken every 5 seconds for 10 hours.

This graph is a result of the module being inserted into an 86100D just before the test was started. Obviously, with a change of 6V at the start of the test one would want to perform a quick module calibration.

Example Script

Copy

remote-head-temperature-graph.py

# -*- coding: utf-8 -*-
""" Requires:
   - an N1045A electrical or N1055A TDR module.
   - Must enter channel number in script's CHANNEL constant.
Graphs the temperature of a remote head over time. This script:
- Confirms that required modules are installed.
- Discovers available channel. Prompts for module to monitor.
- Reports temperature changes in remote head in remote_head_temperatures.png in scripts folder.'
"""

from decimal import Decimal
import matplotlib.pyplot as plt
import math
import sys
import time
import pyvisa as visa  # import VISA library

ADDRESS = 'TCPIP0::K-N1000A-QA06::hislip0,4880::INSTR'  # DCA-X
CHANNEL = '1A'


def open_flexdca_connection(address):
    """ Opens visa connection to FlexFlexDCA. """
    print('Connecting to Flexdca ...')
    try:
        rm = visa.ResourceManager()
        connection = rm.open_resource(address)
        connection.timeout = 20000  # Set connection timeout to 20s
        connection.read_termination = '\n'
        connection.write_termination = '\n'
        inst_id = connection.query('*IDN?')
        print('\nFlexDCA connection established to:\n' + inst_id, flush=True)
    except (visa.VisaIOError, visa.InvalidSession):
        print('\nVISA ERROR: Cannot open instrument address.\n', flush=True)
        return None
    except Exception as other:
        print('\nVISA ERROR: Cannot connect to instrument:', other, flush=True)
        print('\n')
        return None
    return connection


def get_test_parameters():
    """ Return how long a test takes and how many seconds between measurements.
    """
    minutes = get_total_test_time()
    if not minutes:
        return '', 0, 0  # abort program
    seconds = get_time_between_measurements()
    return minutes, seconds


def get_total_test_time():
    """ Asks user for total test time in minutes. """
    while(True):
        minutes = input('How many minutes must test run? ')
        minutes = minutes.strip('"\' ')
        try:
            if minutes == 'q':
                return 0
            minutes = float(minutes)
            return minutes
        except ValueError:
            return 0


def get_time_between_measurements():
    """
    Asks user for time between each measurement in seconds. The
    float conversion tests for a valid number string and if
    not throws an exception. Conversion fails if not a proper number string.
    """
    while(True):
        seconds = input('How many seconds between each measurement? ')
        seconds = seconds.strip('"\' ')
        try:
            if seconds == 'q':
                return 0
            return float(seconds)
        except ValueError:
            return 0


def monitor_temperature(channel, minutes, seconds):
    """ Monitors the temperature for required minutes by performing a
    measurement every seconds. channel is a string, minutes (float) and
    seconds (int).
    """
    global FlexDCA
    DEGREE = '\u00B0'  # °
    DELTAU = '\u0394'  # Δ
    print('Measuring remote head\'s temperature. Please wait.\n')
    sys.stdout.flush()
    thermal_data = []  # list of Decimals
    start_temp = Decimal(FlexDCA.query(':CAL:CHAN'+channel+':STAT:DTEM?'))
    start = time.time()
    first_pass = True
    elapsed_time = 0
    i = 1  # measurement count
    while elapsed_time < (minutes * 60):
        current_temp = Decimal(FlexDCA.query(':CAL:CHAN' +
                                             channel +
                                             ':STAT:DTEM?'))
        change = current_temp - start_temp
        thermal_data.append(change.quantize(Decimal('0.01')))
        s = str(thermal_data[-1])
        results = s + DEGREE + 'C'
        if s[0] == '-':
            print(str(i) + '.  ' + DELTAU + ' = ' + results, flush=True)
        else:
            print(str(i) + '.  ' + DELTAU + ' =  ' + results, flush=True)
        i += 1
        elapsed_time = time.time() - start
        if elapsed_time > 60.0 and first_pass:
            first_pass = False
            if math.fabs(thermal_data[-1]) > 0.5:
                print('\nThe remote head\'s temperature has \
changed more than\n 0.5C-per-minute. \
A module calibration is recommended.\n')
                sys.stdout.flush()
        time.sleep(seconds)
    print(str(len(thermal_data)) + ' temperature readings were taken.')
    sys.stdout.flush()
    return thermal_data


def create_time_data(number_of_data_points, minutes, time_sep):
    """
    Creates list of time data and scales it for seconds, minutes, or hours
    units. The units (xunits) is returned.
    number_of_data_points argument is an int.
    minutes argument is the test time (float).
    time_sep argument is the seconds (int) between data points.
    """
    xunits = ''
    time_data = []  # list of times for each data point
    if 0 < minutes < 5:  # 5 minutes
        time_data = [(i*time_sep) for i in range(0, number_of_data_points)]
        xunits = 'seconds'
    elif 5 <= minutes < 180:  # 5 to 180 minutes (3 hours)
        time_data = [(i*time_sep / 60) for i in range(0,
                                                      number_of_data_points)]
        xunits = 'minutes'
    else:  # >= 3 hours
        time_data = [(i*time_sep / 3600) for i in range(0,
                                                        number_of_data_points)]
        xunits = 'hours'
    return time_data, xunits


def plot_thermal_data(y_data, x_data, xunits, channel):
    """
    y_data argument is a list of Decimals of temperature readings.
    x_data argument is corresponding time positions.
    xunits is 'seconds', 'minutes', or 'hours'.
    channel argument is a string. eg, '1A'.
    """
    global FlexDCA
    max_y = float(max(y_data))
    min_y = float(min(y_data))
    if min_y == 0.0 and max_y == 0.0:
        max_y = 1.0
        min_y = -1.0
    else:
        yrange = max_y - min_y
        yspace = yrange * 0.1
        max_y = max_y + yspace
        min_y = min_y - yspace
    model = FlexDCA.query('SYST:MODel? SLOT' + channel[0])
    title = 'Graph of Remote Head Temperature\n'
    title = title + model + ' Channel '+channel+'\n'
    title = title + 'Test time: '+str(round(x_data[-1], 1))+' ' + xunits
    plt.figure(figsize=(8, 6), dpi=80)
    plt.ylim(min_y, max_y)
    plt.grid(b=True, which='major', axis='both')
    plt.title(title)
    plt.xlabel(xunits.capitalize())
    plt.ylabel('Temperature (Delta C)')
    if len(y_data) < 20:
        plt.plot(x_data, y_data, 'g.-')  # data points shown
    else:
        plt.plot(x_data, y_data, 'g-')  # data points hidden. line only
    plt.savefig('remote_head_temperatures.png', dpi=300)
    plt.savefig('remote_head_temperatures.ps', dpi=300)
    print('The file "remote_head_temperatures.png" has been saved.')
    print('The file "remote_head_temperatures.ps" has been saved.')


FlexDCA = open_flexdca_connection(ADDRESS)
test_time, time_sep = get_test_parameters()
if test_time and time_sep:
    print('Capturing channel ' + CHANNEL + ' data.')
    y_data = monitor_temperature(CHANNEL, test_time, time_sep)
    x_data, xunits = create_time_data(len(y_data), test_time, time_sep)
    plot_thermal_data(y_data, x_data, xunits, CHANNEL)
    print('Program has successfully completed.')
else:
    print('Error. Test time not correct.')
FlexDCA.write(':SYSTem:GTLocal')
FlexDCA.close()