Module hvps_lib

Expand source code
# py-hvps-interface GUI of the Project Peta-pico-Voltron
# petapicovoltron.com
# Copyright 2017-2020 Samuel Rosset
# Distributed under the terms of the GNU General Public License GNU GPLv3

# This file is part of py-hvps-interface.

#    py-hvps-interface 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.
#
#    py-hvps-interface 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 py-hvps-interface.  If not, see  <http://www.gnu.org/licenses/>

try:
    import serial
except ImportError:
    serial = None
    print("Serial library missing. You need to install pySerial")
    exit()
import serial.tools.list_ports
from time import sleep
from math import sin
from math import pi

EMUL = False        # Set to True to test the library without a connected HVPS
DEBUG = False
LIB_VER = "2.9"  # Version of this library (matches the Labview Library number)
FIRM_VER = 9  # Firmware version That this library expects to run on the HVPS

# S witching mode constants: off=0, on-DC=1, on-Switching=2, on-waveform=3
SWMODE_OFF = 0
SWMODE_DC = 1
SWMODE_SW = 2
SWMODE_WFRM = 3

# Switching source constants: Timer=0,External=1, Button=2,
SWSRC_TMR = 0
SWSRC_EXT = 1
SWSRC_BTTN = 2


# Voltage control mode constants: Regulated=0, External=1, Open loop=2
VMODE_R = 0
VMODE_EXT = 1
VMODE_O = 2

# Stroboscobic illumination mode: Off=0 Fixed position=1, sweeping mode=2
STMODE_OFF = 0
STMODE_FIXED = 1
STMODE_SWEEP = 2

# Standard user-defined functions
FUNC_SINE = 0       # sine + offset
FUNC_TRI = 1        # Triangle
FUNC_TRAP = 2       # Trapezoid
FUNC_CSTM = 3       # Custom waveform (read from file)


# Error bits
ERR_FIRM = 0b1  # Incompatibility between python library and Firmware running on HVPS
ERR_TYPE = 0b10  # The connected arduino is not a single channel HVPS
ERR_JACK = 0b100  # The power Jack is not plugged
ERR_COM = 0b1000  # Communication error: The arduino is detected but cannot talk to it. Is arduino running the shvps
# firmware?
ERR_CONF = 0b10000  # Configuration error: the HVPS running the hvps software, but appears to be unconfigured
ERR_PORT = 0b100000  # port cannot be open. Used by another process


class HVPS:
    """ Class to control a petapicovoltron HVPS

    The class implements the functions from https://petapicovoltron.com/software/direct-communication-with-the-hvps/
    for easy communication with the HVPS. In addition, a few higher-level functions are provided, especially
    for the user-defined waveform."""
    # ====Communication functions and class constructor====
    def __init__(self, port):
        """ Initialises a HVPS object: dev = HVPS('/path/to/serial/port').

        Input: COM port to which the HVPS is connected. You can use /dev/null if using in emulator mode (EMUL = True)"""
        self.name = ''
        self.i2c = ''
        self.vmax = 0
        self.swmode = 0
        self.vset = 0
        self.vnow = 0
        self.f = 0
        self.cycles = 0
        self.cycle_n = 0  # the current cycle number
        self.swsrc = 0
        self.vmode = 0
        self.latch = 0  # latching behaviour of the button
        self.err = 0
        self.stmode = 0  # Strobe mode
        self.stpos = 0  # Strobe position
        self.stdur = 0  # strobe duration
        self.stsweep = 5000  # Strobe sweep time (default when HVPS starts)
        self.config = 0  # 0 0=HVPS powered with external power_supply 1=HVPS integrated with touchscreen (standalone mode)
        self.listen = 0  # in touchscreen + battery mode, listen=1 if interface must listen on Pi's serial port
        # for incoming commands
        self.ser = serial.Serial()  # the serial connection to the HVPS
        self.waveform_pts = []  # list to store the set points of the custom waveform
        self.waveform_meas = []     # list to store the measured points of the custom waveform.

        if not EMUL:
            try:
                self.ser = serial.Serial(port, 115200, timeout=1)
            except serial.SerialException:
                self.err = self.err | ERR_PORT
                if DEBUG:
                    print("Cannot connect to serial port. Probably busy.")
            else:
                self.ser.reset_input_buffer()
                if DEBUG:
                    text = "connecting to " + port
                    print(text)
                    print("Serial port open")

                self.ser.write(b'QVer\r')  # We send a command to see if board answers
                sleep(0.1)
                if self.ser.in_waiting == 0:  # if no reply then the connected board is not running a HVPS firmware
                    self.err = self.err | ERR_COM
                else:  # communication is established and board replies to commands
                    self.ser.reset_input_buffer()
                    if self.q_type() != "slave":  # check that the HVPS connected is a SHVPS
                        self.err = self.err | ERR_TYPE
                    else:
                        self.name = self.q_name()  # reads the name of the board
                        if self.name == b'':  # empty name is taken to mean that the board is un-configured
                            self.err = self.err | ERR_CONF
                        else:
                            if self.q_ver() != FIRM_VER:  # HVPs not running the correct firmware version
                                self.err = self.err | ERR_FIRM
                            if not self.q_jack() and self.q_power_jack():  # We don't check for presence of power jack
                                # if HVPS powered via 5V pin (and not jack). self.config cannot be used at this time as
                                # not initialised
                                self.err = self.err | ERR_JACK
                            self._initialise()  # initialise the board (note that ERR_FIRM and ERR_JACK
                            # lead to initialisation

    def close(self):  # closes connection with the HVPS
        """Closes the connection to the HVPS. Sets voltage to 0 if board was connected"""
        if not EMUL:
            if self.ser.is_open:
                if not (self.err & ERR_COM):  # if connected board is not running the firmware, do not send command
                    self.s_vset(0)  # set the voltage to 0 as a safety measure
                self.ser.close()

        if DEBUG:
            print("Serial port closed")

    def transmit(self, x):  # transmits a command directly to the HVPS
        """Sends a direct command to the HVPS

        Sends a command to the HVPS. List of commands:
        https://petapicovoltron.com/software/direct-communication-with-the-hvps/
        This function is used by the main() functions in case this file is run as a script
        and provides an interactive terminal session with the HVPS. However, when using
        the library, the specific functions provided by the library should be used instead\n
        input: x: a string to send to the HVPS\n
        output: The reply from the HVPS (string)"""
        if EMUL:
            z = b'null\r\n'
        else:
            self.ser.write(x)
            z = self._readline2()
        if DEBUG:
            print(x)
            print(z)
        return z

    def _readline(self):  # reads a line on the serial port and removes the end of line characters
        line = self._readline2()
        line = line[:-2]  # removes the last 2 elements of the array because it is \r\n
        return bytes(line)

    def _readline2(self):  # reads a line on the serial port
        eol = b'\r\n'  # Arduino println command adds \r\n at the end of lines
        leneol = len(eol)
        line = bytearray()
        while True:
            c = self.ser.read(1)
            if c:
                line += c
                if line[-leneol:] == eol:
                    break
            else:
                break
        return bytes(line)

    def _initialise(self):
        self.vmax = self.q_vmax()
        self.i2c = self.q_i2c()
        self.f = self.q_f()
        self.config = not self.q_power_jack()  # get the configuration of the HVPS (1= touchscreen + battery).

        self.latch = self.q_latch_mode()
        self.stmode = self.q_st_mode()  # Strobe mode is not saved in memory. But it is not necessarily 0,
        # if the interface was closed, but the HVPS not unplugged
        self.stpos = self.q_st_pos()
        self.stdur = self.q_st_dur()
        self.stsweep = self.s_st_sweep(self.stsweep)
        if self.config == 0:  # if HVPS connected via USB
            self.vset = self.s_vset(0)
            self.swmode = self.s_sw_mode(SWMODE_DC)
            self.swsrc = self.s_sw_src(SWSRC_TMR)
            self.vmode = self.s_v_mode(VMODE_R)
            self.cycles = self.s_cycle(0)
        else:  # if in touchscreen housing, then we use the current parameters (i.e. memory if just
            # started or last params if interface is restarted)
            self.vset = self.q_vset()  # initialises the info structure with the current parameters of the HVPS
            self.swmode = self.q_sw_mode()
            self.swsrc = self.q_sw_src()
            self.vmode = self.q_v_mode()
            s = self.q_cycle()
            self.cycles = int(s[1])
        if DEBUG:
            print("Bord initialised")

    # ====Commands related to voltage and frequency====
    def s_vset(self, x):  # sets the output voltage
        """ Sets the output voltage of the HVPS

        input: x: voltage set point (int)\n
        output: voltage set point accepted by HVPS\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        Although this command can be used at any time, it is mainly useful when the HVPS voltage control mode is
        internal regulator (VMODE_R); see s_vmode() command."""
        if EMUL:
            z = x
        else:
            x = constrain(x, 0, self.vmax)
            string = 'SVset '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_vset(" + str(x) + ") -> " + str(z)
            print(y)
        self.vset = z
        return z

    def q_vset(self):  # queries the voltage setpoint
        """Queries the voltage set point. The returned value is in volts."""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QVset\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_vset -> " + str(z)
            print(y)
        self.vset = z
        return z

    def q_vnow(self):  # queries the voltage output
        """Queries the current feedback voltage of the HVPS.

        The returned value is in volts.
        Note that it is not necessarily the voltage at the output of the HVPS, which also depends on the
        switching mode (whether the HVPS is off, in DC mode, or switching)."""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QVnow\r')
            try:
                z = int(self._readline())
            except ValueError:
                z = 0
        if DEBUG:
            y = "q_vnow -> " + str(z)
            print(y)
        self.vnow = z
        return z

    def s_pwm(self, x):  # sets the pwm value
        """ Set the voltage output as a PWM value

        Defines the functioning set point of the HV programmable source as a 10-bit (0-1023) raw PWM value.
        Although this command can be used at any time, it mainly useful when the HVPS voltage control mode is
        internal openloop, VMODE_O (see set_vmode() command).\n
        input: x: PWM set point (0-1023)\n
        output: PWM set point accepted by HVPS"""
        if EMUL:
            z = 0
        else:
            x = constrain(x, 0, 1023)
            string = 'SPWM '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_pwm -> " + str(z)
            print(y)
        return z

    def q_pwm(self):  # queries the pwm setting
        """Queries the current HVPS set point as a raw PWM value. returns value between 0 and 1023"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QPWM\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_pwm -> " + str(z)
            print(y)
        return z

    def s_f(self, x):  # sets the frequency
        """Sets the frequency of the signal

        Sets the frequency of the signal when the HVPS is in switching mode (SWMODE_SW).\n
        The value returned is the new frequency, taking quantification into account.\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: frequency in Hz between 0.001 and 1000\n
        output: frequency accepted by HVPS"""
        if EMUL:
            z = 1
        else:
            x = constrain(x, 0.001, 1000.0)
            string = 'SF '
            string = string + f"{x:.3f}" + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_f(" + str(x) + ") -> " + str(z)
            print(y)
        self.f = z
        return z

    def q_f(self):  # queries the frequency
        """Queries the switching frequency. The returned value is in Hz."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QF\r')
            z = float(self._readline())
        if DEBUG:
            y = "q_f -> " + str(z)
            print(y)
        self.f = z
        return z

    def s_cycle(self, x):  # sets the number of cycles
        """Sets the number of switching cycles

        Sets the number of switching cycles to perform when the HVPS is in switching mode.
        The maximum value is 65535. A value of 0 means continuous switching (unlimited number of cycles).
        For any value other than 0, the HVPS will change to switching mode 0 (HVPS off, SWMODE_OFF, after the desired
        number of cycles is reached. A new series of switching cycles can be can be initiated by placing the HVPS
        back in switching mode (SWMODE_SW). When you call s_cycle(), the cycle counter is reset to 0.
        Example: s_cycle(1000) to switch 1000 times at the selected frequency and then stop.
        Note that s_cycles configures the HVPS to switch for a limited number of time. If the HVPS is in switching
        mode (SWMODE_SW), then the cycles will start at once. If the HVPS is in off mode (SWMODE_OFF) or in
        DC mode (SWMODE_DC), the cycles will start once the HVPS is put in Switching mode with s_sw_mode(SWMODE_SW).\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: number of cycles (0 to 65535)\n
        output: number of cycles accepted by HVPS"""
        if EMUL:
            z = x
        else:
            x = constrain(x, 0, 65535)
            string = 'SCycle '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_cycle(" + str(x) + ") -> " + str(z)
            print(y)
        self.cycles = z
        return z

    def q_cycle(self):  # queries the number of cycles returns a list. Element 0 is the current cycle number.
        # Element 1 is the total number of cycles in the series.
        """Queries the number of cycles.

        Queries the number of cycles. The returned value is a list z, with z[0] being the current cycle
        number and z[1] the total number of cycles to make. Once the total number of cycles is reached,
        the output is turned off (SWMODE_OFF), and q_cycle() returns [0,XXX] with XXX the number of cycles to make
        (XXX=0 in case of continuous cycles)."""
        if EMUL:
            z = "0/0"
        else:
            self.ser.write(b'QCycle\r')
            z = self._readline()
            z = z.decode("utf-8")
        if DEBUG:
            y = "q_cycle -> " + z
            print(y)
        s = z.split("/")
        self.cycles = s[1]
        self.cycle_n = s[0]
        return s

    # Commands to change the voltage control and switching behaviour
    def s_sw_mode(self, x):  # sets the switching mode
        """Sets the switching mode of the HVPS.

        Sets the switching mode of the HVPS. Four possible values: SWMODE_OFF: HVPS is off (0 V output irrespective
        of voltage set point), SWMODE_DC: the HVPS is in DC mode with a constant output voltage at the desired
        set point, SWMODE_SW: the HVPS is switching at the desired frequency between 0V and Vset, and SWMODE_WFRM:
        the HVPS is in user-defined waveform mode. Setting the switching mode is only effective if the switching
        source is SWSRC_TMR (onboard timer switching).\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: SWMODE_OFF, SWMODE_DC, SWMODE_SW, or SWMODE_WFRM\n
        output: Switching mode set by the HVPS"""
        if EMUL:
            z = x
        else:
            if x > SWMODE_WFRM:
                x = SWMODE_OFF
            string = 'SSwMode '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_sw_mode(" + str(x) + ") -> " + str(z)
            print(y)
        self.swmode = z
        return z

    def q_sw_mode(self):  # queries the switching mode
        """Queries the switching mode of the HVPS.

        Queries the switching mode of the HVPS. Output is either SWMODE_OFF: HVPS is off (0 V output irrespective
        of voltage setpoint), SWMODE_DC the HVPS is in DC mode with a constant output voltage at the desired
        set point, SWMODE_SW: the HVPS is switching at the desired frequency between 0V and Vset, or SWMODE_WFRM:
        the HVPS is in user-defined waveform mode."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QSwMode\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_sw_mode -> " + str(z)
            print(y)
        self.swmode = z
        return z

    def s_sw_src(self, x):  # sets the switching source
        """Sets the source of the switching signal.

        Sets the source of the switching signal. Accepted values are: SWSRC_TMR for onboard switching
        (from internal clock of the board), SWSRC_EXT for external switching via pin 6 (Ext.F) on the board main
        connector, or SWSRC_BTTN for the push button. Example: s_sw_src(SWSRC_EXT) to use the external signal to
        switch the source on/off. Note that this setting is useful only if the jumper on header H2 is set to
        “onboard control”, else the jumper setting defines the source of the switching signal.\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: SWSRC_TMR, or SWSRC_EXT, or SWSRC_BTTN"""
        if EMUL:
            z = x
        else:
            if x > SWSRC_BTTN:
                x = SWSRC_TMR
            string = 'SSwSrc '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_sw_src(" + str(x) + ") -> " + str(z)
            print(y)
        self.swsrc = z
        return z

    def q_sw_src(self):  # queries the switching source
        """Queries the source of the switching signal.

        Queries the source of the switching signal. Output is SWSRC_TMR for onboard switching
        (from internal clock of the board), SWSRC_EXT for external switching via pin 6 (Ext.F) on the board main
        connector, or SWSRC_BTTN for the push button. Example: s_sw_src(SWSRC_EXT) to use the external signal to
        switch the source on/off. Note that this setting is useful only if the jumper on header H2 is set to
        “onboard control”, else the jumper setting defines the source of the switching signal."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QSwSrc\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_sw_src -> " + str(z)
            print(y)
        self.swsrc = z
        return z

    def s_latch_mode(self, x):  # sets the latch mode of the push button
        """Defines the behaviour of the push button

        Defines the behaviour of the push button, when the switching source of the HVPS is set to the push button
        (SWSRC_BTTN, c.f. s_sw_src() command above). Accepted values are 0 and 1: 0 for a push button behaviour
        (i.e. the high voltage is turned on as long as the button is pressed),
        and 1 for a latching switch behaviour (i.e. press once to turn the high voltage on, and press a second time
        to turn it off).\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: 0 or 1\n
        output: latching mode as understood by the HVPS"""
        if EMUL:
            z = x
        else:
            if x > 1:
                x = 1
            string = 'SLatchMode '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_latch_mode(" + str(x) + ") -> " + str(z)
            print(y)
        self.latch = z
        return z

    def q_latch_mode(self):  # queries the latch mode of the push button
        """Queries whether the push button is configured to act as a push button (o) or a latching switch (1)."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QLatchMode\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_latch_mode -> " + str(z)
            print(y)
        self.latch = z
        return z

    def s_v_mode(self, x):  # sets the voltage control mode
        """Sets the voltage control mode

        Sets the voltage control mode (i.e. how is the value of the output voltage controlled):\n
        VMODE_R for internal voltage regulator (regulates the voltage to the value defined with the Vset command).\n
        SMODE_EXT external voltage control (sets the output voltage according to the control voltage applied on pin 5
        (Ext.V) of the main connector of the board. The input voltage range is 0 to 5V.\n
        VMODE_O (that's an O like in open) internal open loop control (on-board regulator disconnected).\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: VMODE_R, VMODE_EXT, VMODE_O\n
        Output: Voltage control mode accepted by the HVPS"""
        if EMUL:
            z = x
        else:
            if x > VMODE_O:
                x = VMODE_R
            string = 'SVMode '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_v_mode(" + str(x) + ") -> " + str(z)
            print(y)
        self.vmode = z
        return z

    def q_v_mode(self):  # queries the switching source
        """Queries the voltage control mode:

        Queries the voltage control mode:\n
        VMODE_R internal voltage regulator, VMODE_EXT external voltage control. VMODE_O internal open loop control
        (on-board regulator disconnected)."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QVMode\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_v_mode -> " + str(z)
            print(y)
        self.vmode = z
        return z

    # ====User Waveform Functions====
    def clear_waveform(self):  # clear the stored waveform
        """Clear the current user-defined waveform

        Clear the current user-defined waveform from the HVPS memory."""
        if EMUL:
            z = 0   # clear waveform returns 0
        else:
            self.ser.write(b'SP X\r')
            z = int(self._readline())
        if DEBUG:
            y = "clear_waveform -> " + str(z)
            print(y)
        return z

    def q_waveform_num_pts(self):  # queries the number of points saved for the waveform
        """Queries how many data point are currently stored in the waveform"""
        if EMUL:
            z = 0   # returns 0 points
        else:
            self.ser.write(b'QPtot\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_waveform_num_pts -> " + str(z)
            print(y)
        return z

    def s_waveform_point(self, x):  # Add a point to the waveform
        """Add a point to the user waveform.

        Add a point to the user waveform. Usage: s_waveform_point(xxx), with xxx between 0 and 255 representing
        0 to 100% of the voltage setpoint.\n
        A new waveform is defined by issuing clear_waveform() (to clear the previous waveform), followed by a series of
        s_waveform_point(xxx) to define the points of the new waveform. The maximal number of allowed points is 255.
        This is a low-level function provided to match the 'SP' command of the HVPS communication protocol. However,
        It is easier to use upload_waveform() to upload a complete waveform to the HVPS in one go, or to use
        upload_std_waveform() to upload some customisable standard waveforms.\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: set point to add to the current waveform. 0-255 representing 0-100% of voltage set point.\n
        output: accepted set point (or -1 if waveform full)"""
        s = [-1, -1]
        x = constrain(x, 0, 255)
        if EMUL:
            z = x
        else:
            num_pts = self.q_waveform_num_pts()   # queries how many points currently saved in memory
            if num_pts < 255:   # can only add a point if less than 255 points currently saved
                string = 'SP '
                string = string + str(x) + '\r'
                string = string.encode()
                self.ser.write(string)
                z = self._readline()
                z = z.decode("utf-8")
                s = z.split(",")
                z = s[1]
            else:
                z = -1
        if DEBUG:
            y = "s_waveform_point(" + str(x) + ") -> " + str(s[0]) + "," + str(s[1])
            print(y)
        return z

    def q_waveform_set_pts(self, x):  # queries the the xth point of the waveform (x starts at 0)
        """queries the waveform set point number x

        queries the waveform set point number x (0<=x<=255) returns a value between 0 and 255 representing 0 to 100%
        of current voltage set point. This is a low-level function provided to match the QP command of the HVPS
        communication protocol. It is easier to use download_waveform_set_pts() to download the whole waveform from
        the HVPS"""
        if EMUL:
            z = x
        else:
            string = 'QP '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "q_waveform_set_point(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_waveform_meas_pts(self, x):  # queries the the measured voltage of the xth point of the
        # waveform (x starts at 0)
        """queries the waveform measured point number x

        queries the waveform measured point number x. Same as q_waveform_set_pts(), but instead of returning the set
        point value, it returns the voltage value read by the SHVPS internally. In order for QR to return meaningful
        values, the SHVPS must have been in Waveform mode (SWMODE_WFRM) for at least one cycle.\n
        Queries the waveform point number x (0<=x<=255). returns a value between 0 and 255 representing 0 to 100%
        of current voltage set point. This is a low-level function provided to match the QR command of the HVPS
        communication protocol. It is easier to use download_waveform_meas_pts() to download the whole waveform from
        the HVPS"""
        if EMUL:
            z = x
        else:
            string = 'QR '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "q_waveform_meas_point(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def upload_waveform(self, x):   # upload a list (x) of waveform set points to the HVPS
        """upload a user-defined waveform to the HVPS

        upload a user-defined waveform to the HVPS. It starts by clearing the current waveform and then it upload
        an new list of points. It also updates the member variable self.waveform_pts with the new list of points.\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: [p1, p2, p3, ..., pn] where pn is the nth point of the waveform, between 0 and 255, representing 0 to
        100% of the current voltage set point. n is limited to 255.\n
        output: none"""
        self.clear_waveform()   # starts by clearing the waveform
        self.waveform_pts.clear()   # empty the current list of point of the local copy
        if len(x) > 255:    # waveform limited to 255 points
            x = x[:255]
        for point in x:
            point = constrain(point, 0, 255)
            self.waveform_pts.append(self.s_waveform_point(point))      # upload the point and add it to the local copy

    def download_waveform_set_pts(self):   # download the waveform set points stored in the HVPS
        """Download the current waveform from the HVPS

        Download the current waveform from the HVPS. The output is a list of set points between 0 and 255 representing
        0 to 100% of the voltage set point. the downloaded points are stored in the member list waveform_pts"""
        self.waveform_pts.clear()   # empty the current list of point of the local copy
        num_points = self.q_waveform_num_pts()
        for i in range(num_points):
            self.waveform_pts.append(self.q_waveform_set_pts(i))      # download the point +add it to the local copy

    def download_waveform_meas_pts(self):   # download the waveform set points stored in the HVPS
        """Download the measured waveform (last performed cycle) from the HVPS

        Download the current waveform from the HVPS. The output is a list of set points between 0 and 255 representing
        0 to 100% of the voltage set point. the SHVPS must have been in Waveform mode (SWMODE_WFRM) for at least one
        cycle to obtain meaningful values. member list waveform_meas"""
        self.waveform_meas.clear()   # empty the current list of point of the local copy
        num_points = self.q_waveform_num_pts()
        for i in range(num_points):
            self.waveform_meas.append(self.q_waveform_meas_pts(i))      # download the point +add it to the local copy

    def upload_std_waveform(self, func=FUNC_SINE, sr=False, n=100,  b=0.15):
        """Upload a customisable standard waveform to the HVPS

        inputs:\n
        func: FUNC_SINE (a sine wave with an offset to be between 0 and Voltage set point), FUNC_TRI
        (a triangle function), FUNC_TRAP (a Trapezoid function), FUNC_CSTM (a custom waveform. Points (a maximum number
        of 255 points) should be defined in a file named waveform.txt located alongside this library. There should be
        1 point per line, each point between 0 and 1, representing 0 to 100% of the voltage set point)\n
        sr: (square root) True or False. In case the HVPS is used to drive dielectric elastomer actuators, there is
        a quadratic relationship between voltage and actuation strain. True: a square root correction is applied so
        that the actuation strain will roughly have the chosen profile. False: No correction applied/n
        n: number of point in the waveform. Max is 255. It depends on the frequency at which the waveform will be
        produced, For a 1Hz signal, 100 points are adequate. Reduce the number of points for higher frequencies
        b: This applies only for the FUNC_TRAP function and defines the percentage of the period that the raising
        (and falling) edge should take. The value should be smaller than 0.5 (at which point the waveform becomes a
        triangle).\n
         The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n"""
        pts = []
        n = constrain(n, 1, 255)     # n must be between 1 and 255 points
        b = constrain(b, 0, 0.5)      # b must be between 0 and 0.5
        if sr:      # if we want the square root of the signal (because of the quadratic relationship between voltage
            #  and strain for DEAs)
            power = 0.5
        else:
            power = 1.0

        if func == FUNC_CSTM:   # custom user waveform
            try:
                fp = open('./waveform.txt', 'r')
            except FileNotFoundError:
                if DEBUG:
                    print("Custom waveform must be in ./waveform.txt, but file is not found")
                fp = 0
            if fp:
                list_of_points = fp.readlines()
                for point in list_of_points:
                    try:
                        point = int(255*(float(point) ** power))
                    except ValueError:
                        point = 0
                        if DEBUG:
                            print("Error when reading point for custom waveform. Each line in the file ./waveform.txt "
                                  "must contain a single floating point number")
                    point = constrain(point, 0, 255)    # points should be between 0 and 255
                    pts.append(point)
                fp.close()
        else:   # if other standard functions are chosen
            for i in range(n):
                p = 0       # p is between 0 to 1 representing percentage of voltage set point
                if func == FUNC_SINE:   # Sine + offset waveform
                    p = (0.5+0.5*sin(2*pi/n*i)) ** power
                elif func == FUNC_TRAP:  # trapeze waveform
                    if i <= b*n:    # the ramp up of the trapeze
                        p = (i/b/n) ** power
                    elif i <= n/2:  # holding time
                        p = 1
                    elif i <= (n/2 + b*n):    # ramp down
                        p = (1-(i - n/2) / b / n) ** power
                    else:
                        p = 0
                elif func == FUNC_TRI:
                    if i <= n/2:    # Raising edge
                        p = (2/n*i) ** power
                    else:
                        p = (1 - 2 / n * (i - n / 2)) ** power
                p = int(p*255)
                pts.append(p)

        self.upload_waveform(pts)       # uploads the waveform to the HVPS

    # ====Trigger / Strobe pulse functions====
    def s_st_mode(self, x):  # sets the strobe mode
        """Sets the strobe mode.

        Sets the strobe mode. Usage: s_st_mode(X), with X being\n
        STMODE_OFF: trigger/strobe pulse off: no signal is generated.\n
        STMODE_FIXED: fixed position: a trigger/strobe signal is generated at a fixed position, with a fixed duration.
        By default, the pulse is synchronized with the HV switching signal, but the position and duration of the pulse
        can be defined by the user (see next s_st_pos()).\n
        STMODE_SWEEP: sweep mode: the position of the trigger/strobe pulse is shifted along the HV signal at a
        user-defined speed. The HVPS must be in switching mode (SWMODE_SW) for the trigger pulse to be generated.
        The trigger pulse is generated on pin T of the multi-purpose 10-pins header on the HVPS board.\n
        input: x: STMODE_OFF or STMODE_FIXED, or STMODE_SWEEP\n
        output: strobe mode as understood by HVPS"""
        if EMUL:
            z = x
        else:
            if x > STMODE_SWEEP:
                x = STMODE_OFF
            string = 'SStMode '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_st_mode(" + str(x) + ") -> " + str(z)
            print(y)
        self.stmode = z
        return z

    def q_st_mode(self):  # queries the strobe mode
        """Queries the current strobe mode.

        Queries the current strobe mode. 0: Off, 1: Fixed position, 2: Sweep mode.\n
        STMODE_OFF: trigger/strobe pulse off: no signal is generated.\n
        STMODE_FIXED: fixed position: a trigger/strobe signal is generated at a fixed position, with a fixed duration.
        By default, the pulse is synchronized with the HV switching signal, but the position and duration of the pulse
        can be defined by the user (see next s_st_pos()).\n
        STMODE_SWEEP: sweep mode: the position of the trigger/strobe pulse is shifted along the HV signal at a
        user-defined speed."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QStMode\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_st_mode -> " + str(z)
            print(y)
        self.stmode = z
        return z

    def s_st_pos(self, x):  # sets the position of the strobe pulse
        """Sets the position of the rising edge of the strobe pulse with respect to the HV output.

        Sets the position of the rising edge of the strobe pulse with respect to the HV output.
        Usage: s_st_pos(xxx), with xxx a value between 0 and 255, representing the position of the rising edge of the
        trigger pulse as a fraction of the period T of the HV signal. A value of 0 means that the rising edge of the
        trigger pulse is coincident with the rising edge of the HV output.\n
        A value of 127 means that the rising edge of the trigger pulse is coincident with the falling edge of the HV
        output. The position of the pulse is only used when the strobe pulse mode is STMODE_FIXED (fixed position).\n
        input: x: position of the trigger pulse (0 to 255)\n
        output: position of the trigger pulse accepted by the HVPS"""
        if EMUL:
            z = x
        else:
            x = constrain(x, 0, 255)
            string = 'SStPos '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_st_pos(" + str(x) + ") -> " + str(z)
            print(y)
        self.stpos = z
        return z

    def q_st_pos(self):  # queries the the position of the strobe pulse
        """Queries the current position of the rising edge of the strobe/trigger pulse.

        Queries the current position of the rising edge of the strobe/trigger pulse. The returned value is
        between 0 and 255, and represents a fraction of the period of the HV output, with 0
        being the rising edge of the HV signal."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QStPos\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_st_pos -> " + str(z)
            print(y)
        self.stpos = z
        return z

    def s_st_dur(self, x):  # sets the duration of the strobe pulse
        """Sets the duration of the strobe/trigger pulse as a fraction of the period of the HV output.

        Sets the duration of the strobe/trigger pulse as a fraction of the period of the HV output.
        Usage: s_st_dur(xxx), with xxx being the fraction of the period of the HV output during which the
        strobe/trigger pulse must remain high
        (0: constantly off, 127: on during 50% of the period, and 255: constantly on).\n
        The duration of the pulse is used in strobe mode ST_MODE_FIXED (fixed position) and STMODE_SWEEP (sweep mode)\n
        input: x: duration of the strobe pulse (0-255, representing 0 to 100% duty cycle)\n
        output: pulse duration accepted by the HVPS"""
        if EMUL:
            z = x
        else:
            x = constrain(x, 0, 255)
            string = 'SStDur '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_st_dur(" + str(x) + ") -> " + str(z)
            print(y)
        self.stdur = z
        return z

    def q_st_dur(self):  # queries the the duration of the strobe pulse
        """Queries the current duration of the strobe/trigger pulse.

        Queries the current duration of the strobe/trigger pulse. Returns a value between 0 and 255 representing
        the fraction of the period of the HV signal during which the strobe/trigger signal remains high
        (0: constantly off, 127: on during 50% of the period, and 255: constantly on)."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QStDur\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_st_dur -> " + str(z)
            print(y)
        self.stdur = z
        return z

    def s_st_sweep(self, x):  # sets the sweep period (im ms) when in sweep mode
        """sets the sweep time in ms.

        sets the sweep time in ms. Usage s_st_sweep(xxx), where xxx is the time  (in ms) that it takes to sweep the
        strobe/trigger pulse over a complete period of the HV output. This parameter is only used in strobe mode
        STMODE_SWEEP (sweep mode). If the pulse is used to drive a strobe LED, the sweep time defines the apparent
        period of the motion. Logically, the period set with s_st_sweep much be considerably longer than the period
        of the HV signal.\n
        input: x: period of the sweep duration in ms\n
        output: sweep duration accepted by HVPS"""
        if EMUL:
            z = x
        else:
            x = constrain(x, 0, 65535)
            string = 'SStSw '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_st_sweep(" + str(x) + ") -> " + str(z)
            print(y)
        self.stsweep = z
        return z

    # Miscellaneous functions
    def save(self):  # save current HVPS parameters into the memory
        """save current HVPS parameters into the memory

        This command saves the following parameters in EEPROM: Voltage setpoint, Frequency, Frequency divider,
        Source of switching signal (external, internal, button), Switching mode (off, DC output, switching output),
        Voltage mode (internal voltage regulator or following external signal), and number of switching cycles.
        It also saves the user-defined waveform into the EEPROM. This is useful so that it is not necessary to
        reconfigure the HVPS for the desired behavior every time it is powered up, for example when the HVPS is
        meant to be used without computer (when connected to a demo device). Note that when the HVPS is used with a
        computer, it is safer to save a voltage setpoint of 0V (and/or a switching mode 0 (box off)) so as to be sure
        that no high voltage is present at the output when the board is powered up (and before the computer has the
        time to initialise it to 0V output)."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'Save\r')
            z = int(self._readline())
        if DEBUG:
            y = "save -> " + str(z)
            print(y)
        return z

    def q_mem(self):  # queries the content of the memory
        """queries the content of the memory

        Queries the content of the memory (when the box is powered up, it will use the settings stored in memory,
        which allows using the board without a computer).
        This commands returns a string of parameters separated by commas: Voltage, Frequency, Switching source,
        Switching mode, Voltage mode, Regulator gain P, Regulator gain I, Regulator gain D ,
        Voltage Calibration factor 0, Voltage Calibration factor 1, Voltage Calibration factor 2,
        number of switching cycles, button latching mode.\n
        input: none\n
        output: a HVPSMem object with the values of the different parameters."""
        mem = HVPSMem()
        if not EMUL:
            self.ser.write(b'QMem\r')
            z = self._readline()
            z = z.decode("utf-8")
            s = z.split(",")
            mem.vset = int(s[0])
            mem.f = float(s[1])
            mem.swsrc = int(s[2])
            mem.swmode = int(s[3])
            mem.vmode = int(s[4])
            mem.kp = float(s[5])
            mem.ki = float(s[6])
            mem.kd = float(s[7])
            mem.c0 = float(s[8])
            mem.c1 = float(s[9])
            mem.c2 = float(s[10])
            mem.cycles = int(s[11])
            mem.latch = int(s[12])
        else:
            z = 'empty memory'
        if DEBUG:
            y = "q_mem -> " + z
            print(y)
        return mem

    def q_ver(self):  # queries the firmware version
        """returns the current version of the firmware running on the board."""
        if EMUL:
            z = 7
        else:
            self.ser.write(b'QVer\r')
            s = self._readline()  # returns a string in the form of "slave X" need to get the value of X
            s = s.decode("utf-8")
            z1 = s.split(" ")
            z = int(z1[1])
        if DEBUG:
            y = "q_ver -> " + str(z)
            print(y)
        return z

    def q_jack(self):  # queries whether power Jack is plugged in.
        """returns 1 if the power adapter is plugged in the Jack socket J1, and 0 if it is not."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QJack\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_jack -> " + str(z)
            print(y)
        return z

    # Configuration functions
    def conf(self, x):  # performs initial configuration of the board
        """Performs the initial configuration of a board

        Performs the initial configuration of a board (instead of using commands below separately).
        Usage: conf(xxx), where XXX is the voltage rating of one of the 5 standard configurations
        (i.e. 5000, 3000, 2000, 1200, and 500). Initialises Vmax to the value of XXX, and C0, C1, C2, Kp, Ki, and Kd
        to the default values for each voltage rating.\n
        input: x: board voltage rating (5000, 3000, 2000, 1200, or 500)
        output: 1 in case of success, 0 in case of failure"""
        if EMUL:
            z = 1  # this emulate success
        else:
            if x == 5000 or x == 3000 or x == 2000 or x == 1200 or x == 500:
                string = 'Conf '
                string = string + str(x) + '\r'
                string = string.encode()
                self.ser.write(string)
                z = int(self._readline()) > 0
                self._initialise()  # initialises the board now that it has been configured
            else:
                z = 0  # 0 indicates failure (in that case entered voltage not one of the HVPS std voltages
        if DEBUG:
            y = "conf(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def s_i2c(self, x):  # sets the I2C address
        """sets the I2C address of the board. Only useful if board to be used in multichannel configuration

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n
        input: x: I2C address to assign"""
        if EMUL:
            z = 12  # random returned i2c value in case we emulate the presence of a board
        else:
            x = constrain(x, 0, 127)
            string = 'SI2C '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_I2C(" + str(x) + ") -> " + str(z)
            print(y)
        self.i2c = z
        return z

    def q_i2c(self):  # queries the i2c address of the board
        """queries the I2C address of the board."""
        if EMUL:
            z = 12
        else:
            self.ser.write(b'QI2C\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_I2C -> " + str(z)
            print(y)
        self.i2c = z
        return z

    def s_vmax(self, x):  # sets the maximum voltage rating of the board
        """sets the voltage rating of the SHVPS.

        sets the voltage rating of the SHVPS. Must match the EMCO DC/DC converter rating.\n
        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n
        input: x:voltage rating of HVPS in Volt"""
        if EMUL:
            z = x
        else:
            x = constrain(x, 0, 10000)
            string = 'SVmax '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_vmax(" + str(x) + ") -> " + str(z)
            print(y)
        self.vmax = z
        return z

    def q_vmax(self):  # queries the voltage rating of the board
        """Queries the maximal voltage of the board. The returned value is in volts."""
        if EMUL:
            z = 5000
        else:
            self.ser.write(b'QVmax\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_vmax -> " + str(z)
            print(y)
        self.vmax = z
        return z

    def s_c0(self, x):  # sets the calibration constant c0
        """sets the calibration constant c0

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
        if EMUL:
            z = x
        else:
            string = 'SC0 '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_c0(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_c0(self):  # queries the calibration constant c0
        """queries the calibration constant c0"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QC0\r')
            z = float(self._readline())
        if DEBUG:
            y = "q_c0 -> " + str(z)
            print(y)
        return z

    def s_c1(self, x):  # sets the calibration constant c1
        """sets the calibration constant c1

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
        if EMUL:
            z = x
        else:
            string = 'SC1 '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_c1(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_c1(self):  # queries the calibration constant c1
        """queries the calibration constant c1"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QC1\r')
            z = float(self._readline())
        if DEBUG:
            y = "q_c1 -> " + str(z)
            print(y)
        return z

    def s_c2(self, x):  # sets the calibration constant c2
        """sets the calibration constant c2

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
        if EMUL:
            z = x
        else:
            string = 'SC2 '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_c2(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_c2(self):  # queries the calibration constant c2
        """queries the calibration constant c2"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QC2\r')
            z = float(self._readline())
        if DEBUG:
            y = "q_c2 -> " + str(z)
            print(y)
        return z

    def s_kp(self, x):  # sets the PID gain Kp
        """sets parameters Kp of the PID regulator.

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
        if EMUL:
            z = x
        else:
            string = 'SKp '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_kp(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_kp(self):  # queries the PID gain Kp
        """Query the value of parameter Kp of the PID regulator"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QKp\r')
            z = float(self._readline())

        if DEBUG:
            y = "q_kp -> " + str(z)
            print(y)
        return z

    def s_ki(self, x):  # sets the PID gain Ki
        """""sets parameters Ki of the PID regulator.

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
        if EMUL:
            z = x
        else:
            string = 'SKi '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_ki(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_ki(self):  # queries the PID gain Ki
        """Query the value of parameter Ki of the PID regulator"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QKi\r')
            z = float(self._readline())
        if DEBUG:
            y = "q_ki -> " + str(z)
            print(y)
        return z

    def s_kd(self, x):  # sets the PID gain Kd
        """sets parameters Kd of the PID regulator.

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
        if EMUL:
            z = x
        else:
            string = 'SKd '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_kd(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_kd(self):  # queries the PID gain Kd
        """Query the value of parameter Kd of the PID regulator"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QKd\r')
            z = float(self._readline())
        if DEBUG:
            y = "q_kp -> " + str(z)
            print(y)
        return z

    def s_name(self, x):  # set the name of the HVPS
        """Sets the name of the HVPS.

        Sets the name of the HVPS. 20 characters maximum\n
        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n
        input: x=Name of the HVPS
        output: name accepted by HVPS"""
        if EMUL:
            z = x
        elif len(x) < 21:
            string = 'SName '
            string = string + x + '\r'
            string = string.encode()
            self.ser.write(string)
            z = self._readline()
        else:
            z = 'too long'
        if DEBUG:
            y = "s_kd(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_name(self):  # queries the name of the board
        """queries the name of the board"""
        if EMUL:
            z = "Emulator"
        else:
            self.ser.write(b'QName\r')
            z = self._readline()
        if DEBUG:
            y = "q_name -> " + str(z)
            print(y)
        self.name = z
        return z

    def q_type(self):  # queries the type (master (multi-channel), slave (single channel))
        """queries the type (master (multi-channel), slave (single channel))"""
        if EMUL:
            z = "slave"
        else:
            self.ser.write(b'QVer\r')
            s = self._readline()  # returns a string in the form of "slave X" need to get slave
            s = s.decode("utf-8")
            z1 = s.split(" ")
            z = z1[0]
        if DEBUG:
            y = "q_ver -> " + str(z)
            print(y)
        return z

    def s_power_jack(self, x):  # sets the configuration of the HVPS (whether power comes from power jack)
        """Defines if power if provided by the power jack.

        Defines if power if provided by the power jack, s_power_jack(1) (default), or through 5V pin, s_power_jack(0).
        As the default is power jack 1, You only need the use this command with SPowJack 0 if you wish to use the HVPS
        in the standalone configuration with touchscreen and battery, or in the multichannel configuration.\n
        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n
        input: x: 0 or 1
        output: power jack mode accepted by HVPS"""
        if EMUL:
            z = x
        else:
            if x > 1:
                x = 1
            string = 'SPowJack '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_power_jack(" + str(x) + ") -> " + str(z)
            print(y)
        self.config = not z     # updates the configuration status of the HVPS
        return z

    def q_power_jack(self):  # queries whether power is supposed to come from Power Jack.
        # (0 if in touchscreen + battery configuration)
        """Queries the power jack setting.

        Queries the power jack setting. 1 when HVPS expects to receive power from power jack, and 0 when power
        comes from 5 V pin."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QPowJack\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_power_jack -> " + str(z)
            print(y)
        return z


class HVPSMem:  # structure to store the memory information of the HVPS
    def __init__(self):
        self.vset = 0
        self.f = 0
        self.swsrc = 0
        self.swmode = 0
        self.vmode = 0
        self.kp = 0
        self.ki = 0
        self.kd = 0
        self.c0 = 0
        self.c1 = 0
        self.c2 = 0
        self.cycles = 0
        self.latch = 0
        self.vmax = 0  # Vmax, ver and i2c address are not returned by the q_mem function, because they have dedicated
        # query function. They are added to the structure so that all information regarding the configuration of the
        # board can be grouped in a single variable. (Name should be added)
        self.i2c = 0
        self.ver = 0


def list_hvps(list_all=False):
    """list the arduinos and serial to usb converter connected to PC in a dictionary.

    The key is the name of the HVPS, and the parameter it the associated serial port
    list arduinos connected to PC in an array. It also list the generic usb to serial controller used in the
    touchscreen configuration.
    input:
    list_all: if True list all connected HVPS.
    if list_all is false, it will only list device that are configured. Using True enable to list unconfigured
    devices and give the opportunity to configure them"""
    arduino_ports = [  # creates a list with all of the arduino connected to the computer
        p.device
        for p in serial.tools.list_ports.comports()
        if ('Arduino' in p.description or 'Serial' in p.description)
    ]
    dict_hvps = {}
    i = 0
    for port in arduino_ports:
        dev = HVPS(port)    # Attempts to connect to HVPS
        if not dev.err or dev.err & ERR_FIRM or dev.err & ERR_JACK:     # No error (Firmware mismatch or absence of
            # Power jack OK): looks to be a HVPS to which we can connect
            dict_hvps[dev.name.decode("utf-8")] = port        # add an entry in the dictionary with the name of the
            # HVPS and the port
        elif list_all and not (dev.err & ERR_PORT) and not (dev.err & ERR_COM):      # Also list unconfigured boards
            name = 'Noname' + str(i)
            i = i+1
            dict_hvps[name] = port
        dev.close()

    return dict_hvps


def interactive(dev):
    """provides a terminal for direct communication with a HVPS

    provides a terminal for direct communication with a HVPS\n
    input: dev: a HVPS object with which to communicate"""
    print("entering interactive mode. Enter commands to send to HVPS. type exit to quit")
    stop = False
    while not stop:
        data_str = input()
        if data_str == "exit":
            stop = True
        else:
            data_str = data_str + "\r"
            data_str = data_str.encode()
            hvps_answer = dev.transmit(data_str)  # forwards commands to HVPS
            hvps_answer = hvps_answer[:-2]  # removes the last 2 elements of the array because it is \r\n
            print(hvps_answer.decode('utf-8'))  # to remove the b prefix


def main():
    cont = False  # flag to decide whether to continue the programme or close the COM port
    dev = None
    if EMUL:
        dev = HVPS('/dev/NULL')
    else:
        ports = list_hvps(list_all=True)
        keys = list(ports.keys())
        if len(ports) == 0:
            print("No HVPS connected to the device. Terminating programme")
            exit()
        elif len(ports) == 1:
            dev = HVPS(ports[keys[0]])     # connects to the only available HVPS
        else:
            print("List of connected HVPS:")
            for i in range(len(ports)):
                print(str(i) + ": " + keys[i])
            print("Enter the number of the board you want to connect to")
            connect_to = 0
            try:
                connect_to = int(input())
            except ValueError:
                print("Invalid entry. Terminating programme")
                exit()
            if connect_to >= len(ports):
                print("Invalid entry. Terminating programme")
                exit()
            else:
                dev = HVPS(ports[keys[connect_to]])
    error = dev.err
    if error & ERR_PORT:
        print("Cannot open COM port. Probably busy with another process. Terminating Programme")
    if error & ERR_COM:
        print("Cannot communicate with HVPS. Is it loaded with the shvps firmware?. Terminating programme")
    if error & ERR_TYPE:
        print("Multi chanel HVPS not supported. Connect a single channel HVPS. Terminating programme")
    if error & ERR_CONF:
        print("HVPS appears not to be configured. Do you want to configure board? Y/N")
        if input() == 'Y':
            print("Enter HVPS voltage rating (5000, 3000, 2000, 1200, or 500)")
            answer = int(input())
            if answer == 5000 or answer == 3000 or answer == 2000 or answer == 1200 or answer == 500:
                if dev.conf(answer):  # config function returns 1 if successful
                    print("Configuration successful, continuing")
                    cont = True  # We are good to continue
                else:
                    print("Configuration failed. Terminating programme")
            else:
                print("Wrong voltage value entered. Terminating programme")
        else:
            print("HVPS left unconfigured. Terminating programme")
    if error & ERR_JACK:
        print("Warning: Power Jack not connected. Connect HVPS to power")
        cont = True
    if error & ERR_FIRM:
        print("Warning: HVPS firmware incompatible with this library. You should upgrade you HVPS")
        cont = True
    if error == 0:
        cont = True
    if cont:
        interactive(dev)
    dev.close()


def constrain(val, min_val, max_val):
    """A simple implementation to constrain a value between two boundaries"""
    return min(max_val, max(min_val, val))


if __name__ == "__main__":  # if the library is executed as the main programme
    main()

Functions

def constrain(val, min_val, max_val)

A simple implementation to constrain a value between two boundaries

Expand source code
def constrain(val, min_val, max_val):
    """A simple implementation to constrain a value between two boundaries"""
    return min(max_val, max(min_val, val))
def interactive(dev)

provides a terminal for direct communication with a HVPS

provides a terminal for direct communication with a HVPS

input: dev: a HVPS object with which to communicate

Expand source code
def interactive(dev):
    """provides a terminal for direct communication with a HVPS

    provides a terminal for direct communication with a HVPS\n
    input: dev: a HVPS object with which to communicate"""
    print("entering interactive mode. Enter commands to send to HVPS. type exit to quit")
    stop = False
    while not stop:
        data_str = input()
        if data_str == "exit":
            stop = True
        else:
            data_str = data_str + "\r"
            data_str = data_str.encode()
            hvps_answer = dev.transmit(data_str)  # forwards commands to HVPS
            hvps_answer = hvps_answer[:-2]  # removes the last 2 elements of the array because it is \r\n
            print(hvps_answer.decode('utf-8'))  # to remove the b prefix
def list_hvps(list_all=False)

list the arduinos and serial to usb converter connected to PC in a dictionary.

The key is the name of the HVPS, and the parameter it the associated serial port list arduinos connected to PC in an array. It also list the generic usb to serial controller used in the touchscreen configuration. input: list_all: if True list all connected HVPS. if list_all is false, it will only list device that are configured. Using True enable to list unconfigured devices and give the opportunity to configure them

Expand source code
def list_hvps(list_all=False):
    """list the arduinos and serial to usb converter connected to PC in a dictionary.

    The key is the name of the HVPS, and the parameter it the associated serial port
    list arduinos connected to PC in an array. It also list the generic usb to serial controller used in the
    touchscreen configuration.
    input:
    list_all: if True list all connected HVPS.
    if list_all is false, it will only list device that are configured. Using True enable to list unconfigured
    devices and give the opportunity to configure them"""
    arduino_ports = [  # creates a list with all of the arduino connected to the computer
        p.device
        for p in serial.tools.list_ports.comports()
        if ('Arduino' in p.description or 'Serial' in p.description)
    ]
    dict_hvps = {}
    i = 0
    for port in arduino_ports:
        dev = HVPS(port)    # Attempts to connect to HVPS
        if not dev.err or dev.err & ERR_FIRM or dev.err & ERR_JACK:     # No error (Firmware mismatch or absence of
            # Power jack OK): looks to be a HVPS to which we can connect
            dict_hvps[dev.name.decode("utf-8")] = port        # add an entry in the dictionary with the name of the
            # HVPS and the port
        elif list_all and not (dev.err & ERR_PORT) and not (dev.err & ERR_COM):      # Also list unconfigured boards
            name = 'Noname' + str(i)
            i = i+1
            dict_hvps[name] = port
        dev.close()

    return dict_hvps
def main()
Expand source code
def main():
    cont = False  # flag to decide whether to continue the programme or close the COM port
    dev = None
    if EMUL:
        dev = HVPS('/dev/NULL')
    else:
        ports = list_hvps(list_all=True)
        keys = list(ports.keys())
        if len(ports) == 0:
            print("No HVPS connected to the device. Terminating programme")
            exit()
        elif len(ports) == 1:
            dev = HVPS(ports[keys[0]])     # connects to the only available HVPS
        else:
            print("List of connected HVPS:")
            for i in range(len(ports)):
                print(str(i) + ": " + keys[i])
            print("Enter the number of the board you want to connect to")
            connect_to = 0
            try:
                connect_to = int(input())
            except ValueError:
                print("Invalid entry. Terminating programme")
                exit()
            if connect_to >= len(ports):
                print("Invalid entry. Terminating programme")
                exit()
            else:
                dev = HVPS(ports[keys[connect_to]])
    error = dev.err
    if error & ERR_PORT:
        print("Cannot open COM port. Probably busy with another process. Terminating Programme")
    if error & ERR_COM:
        print("Cannot communicate with HVPS. Is it loaded with the shvps firmware?. Terminating programme")
    if error & ERR_TYPE:
        print("Multi chanel HVPS not supported. Connect a single channel HVPS. Terminating programme")
    if error & ERR_CONF:
        print("HVPS appears not to be configured. Do you want to configure board? Y/N")
        if input() == 'Y':
            print("Enter HVPS voltage rating (5000, 3000, 2000, 1200, or 500)")
            answer = int(input())
            if answer == 5000 or answer == 3000 or answer == 2000 or answer == 1200 or answer == 500:
                if dev.conf(answer):  # config function returns 1 if successful
                    print("Configuration successful, continuing")
                    cont = True  # We are good to continue
                else:
                    print("Configuration failed. Terminating programme")
            else:
                print("Wrong voltage value entered. Terminating programme")
        else:
            print("HVPS left unconfigured. Terminating programme")
    if error & ERR_JACK:
        print("Warning: Power Jack not connected. Connect HVPS to power")
        cont = True
    if error & ERR_FIRM:
        print("Warning: HVPS firmware incompatible with this library. You should upgrade you HVPS")
        cont = True
    if error == 0:
        cont = True
    if cont:
        interactive(dev)
    dev.close()

Classes

class HVPS (port)

Class to control a petapicovoltron HVPS

The class implements the functions from https://petapicovoltron.com/software/direct-communication-with-the-hvps/ for easy communication with the HVPS. In addition, a few higher-level functions are provided, especially for the user-defined waveform.

Initialises a HVPS object: dev = HVPS('/path/to/serial/port').

Input: COM port to which the HVPS is connected. You can use /dev/null if using in emulator mode (EMUL = True)

Expand source code
class HVPS:
    """ Class to control a petapicovoltron HVPS

    The class implements the functions from https://petapicovoltron.com/software/direct-communication-with-the-hvps/
    for easy communication with the HVPS. In addition, a few higher-level functions are provided, especially
    for the user-defined waveform."""
    # ====Communication functions and class constructor====
    def __init__(self, port):
        """ Initialises a HVPS object: dev = HVPS('/path/to/serial/port').

        Input: COM port to which the HVPS is connected. You can use /dev/null if using in emulator mode (EMUL = True)"""
        self.name = ''
        self.i2c = ''
        self.vmax = 0
        self.swmode = 0
        self.vset = 0
        self.vnow = 0
        self.f = 0
        self.cycles = 0
        self.cycle_n = 0  # the current cycle number
        self.swsrc = 0
        self.vmode = 0
        self.latch = 0  # latching behaviour of the button
        self.err = 0
        self.stmode = 0  # Strobe mode
        self.stpos = 0  # Strobe position
        self.stdur = 0  # strobe duration
        self.stsweep = 5000  # Strobe sweep time (default when HVPS starts)
        self.config = 0  # 0 0=HVPS powered with external power_supply 1=HVPS integrated with touchscreen (standalone mode)
        self.listen = 0  # in touchscreen + battery mode, listen=1 if interface must listen on Pi's serial port
        # for incoming commands
        self.ser = serial.Serial()  # the serial connection to the HVPS
        self.waveform_pts = []  # list to store the set points of the custom waveform
        self.waveform_meas = []     # list to store the measured points of the custom waveform.

        if not EMUL:
            try:
                self.ser = serial.Serial(port, 115200, timeout=1)
            except serial.SerialException:
                self.err = self.err | ERR_PORT
                if DEBUG:
                    print("Cannot connect to serial port. Probably busy.")
            else:
                self.ser.reset_input_buffer()
                if DEBUG:
                    text = "connecting to " + port
                    print(text)
                    print("Serial port open")

                self.ser.write(b'QVer\r')  # We send a command to see if board answers
                sleep(0.1)
                if self.ser.in_waiting == 0:  # if no reply then the connected board is not running a HVPS firmware
                    self.err = self.err | ERR_COM
                else:  # communication is established and board replies to commands
                    self.ser.reset_input_buffer()
                    if self.q_type() != "slave":  # check that the HVPS connected is a SHVPS
                        self.err = self.err | ERR_TYPE
                    else:
                        self.name = self.q_name()  # reads the name of the board
                        if self.name == b'':  # empty name is taken to mean that the board is un-configured
                            self.err = self.err | ERR_CONF
                        else:
                            if self.q_ver() != FIRM_VER:  # HVPs not running the correct firmware version
                                self.err = self.err | ERR_FIRM
                            if not self.q_jack() and self.q_power_jack():  # We don't check for presence of power jack
                                # if HVPS powered via 5V pin (and not jack). self.config cannot be used at this time as
                                # not initialised
                                self.err = self.err | ERR_JACK
                            self._initialise()  # initialise the board (note that ERR_FIRM and ERR_JACK
                            # lead to initialisation

    def close(self):  # closes connection with the HVPS
        """Closes the connection to the HVPS. Sets voltage to 0 if board was connected"""
        if not EMUL:
            if self.ser.is_open:
                if not (self.err & ERR_COM):  # if connected board is not running the firmware, do not send command
                    self.s_vset(0)  # set the voltage to 0 as a safety measure
                self.ser.close()

        if DEBUG:
            print("Serial port closed")

    def transmit(self, x):  # transmits a command directly to the HVPS
        """Sends a direct command to the HVPS

        Sends a command to the HVPS. List of commands:
        https://petapicovoltron.com/software/direct-communication-with-the-hvps/
        This function is used by the main() functions in case this file is run as a script
        and provides an interactive terminal session with the HVPS. However, when using
        the library, the specific functions provided by the library should be used instead\n
        input: x: a string to send to the HVPS\n
        output: The reply from the HVPS (string)"""
        if EMUL:
            z = b'null\r\n'
        else:
            self.ser.write(x)
            z = self._readline2()
        if DEBUG:
            print(x)
            print(z)
        return z

    def _readline(self):  # reads a line on the serial port and removes the end of line characters
        line = self._readline2()
        line = line[:-2]  # removes the last 2 elements of the array because it is \r\n
        return bytes(line)

    def _readline2(self):  # reads a line on the serial port
        eol = b'\r\n'  # Arduino println command adds \r\n at the end of lines
        leneol = len(eol)
        line = bytearray()
        while True:
            c = self.ser.read(1)
            if c:
                line += c
                if line[-leneol:] == eol:
                    break
            else:
                break
        return bytes(line)

    def _initialise(self):
        self.vmax = self.q_vmax()
        self.i2c = self.q_i2c()
        self.f = self.q_f()
        self.config = not self.q_power_jack()  # get the configuration of the HVPS (1= touchscreen + battery).

        self.latch = self.q_latch_mode()
        self.stmode = self.q_st_mode()  # Strobe mode is not saved in memory. But it is not necessarily 0,
        # if the interface was closed, but the HVPS not unplugged
        self.stpos = self.q_st_pos()
        self.stdur = self.q_st_dur()
        self.stsweep = self.s_st_sweep(self.stsweep)
        if self.config == 0:  # if HVPS connected via USB
            self.vset = self.s_vset(0)
            self.swmode = self.s_sw_mode(SWMODE_DC)
            self.swsrc = self.s_sw_src(SWSRC_TMR)
            self.vmode = self.s_v_mode(VMODE_R)
            self.cycles = self.s_cycle(0)
        else:  # if in touchscreen housing, then we use the current parameters (i.e. memory if just
            # started or last params if interface is restarted)
            self.vset = self.q_vset()  # initialises the info structure with the current parameters of the HVPS
            self.swmode = self.q_sw_mode()
            self.swsrc = self.q_sw_src()
            self.vmode = self.q_v_mode()
            s = self.q_cycle()
            self.cycles = int(s[1])
        if DEBUG:
            print("Bord initialised")

    # ====Commands related to voltage and frequency====
    def s_vset(self, x):  # sets the output voltage
        """ Sets the output voltage of the HVPS

        input: x: voltage set point (int)\n
        output: voltage set point accepted by HVPS\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        Although this command can be used at any time, it is mainly useful when the HVPS voltage control mode is
        internal regulator (VMODE_R); see s_vmode() command."""
        if EMUL:
            z = x
        else:
            x = constrain(x, 0, self.vmax)
            string = 'SVset '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_vset(" + str(x) + ") -> " + str(z)
            print(y)
        self.vset = z
        return z

    def q_vset(self):  # queries the voltage setpoint
        """Queries the voltage set point. The returned value is in volts."""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QVset\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_vset -> " + str(z)
            print(y)
        self.vset = z
        return z

    def q_vnow(self):  # queries the voltage output
        """Queries the current feedback voltage of the HVPS.

        The returned value is in volts.
        Note that it is not necessarily the voltage at the output of the HVPS, which also depends on the
        switching mode (whether the HVPS is off, in DC mode, or switching)."""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QVnow\r')
            try:
                z = int(self._readline())
            except ValueError:
                z = 0
        if DEBUG:
            y = "q_vnow -> " + str(z)
            print(y)
        self.vnow = z
        return z

    def s_pwm(self, x):  # sets the pwm value
        """ Set the voltage output as a PWM value

        Defines the functioning set point of the HV programmable source as a 10-bit (0-1023) raw PWM value.
        Although this command can be used at any time, it mainly useful when the HVPS voltage control mode is
        internal openloop, VMODE_O (see set_vmode() command).\n
        input: x: PWM set point (0-1023)\n
        output: PWM set point accepted by HVPS"""
        if EMUL:
            z = 0
        else:
            x = constrain(x, 0, 1023)
            string = 'SPWM '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_pwm -> " + str(z)
            print(y)
        return z

    def q_pwm(self):  # queries the pwm setting
        """Queries the current HVPS set point as a raw PWM value. returns value between 0 and 1023"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QPWM\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_pwm -> " + str(z)
            print(y)
        return z

    def s_f(self, x):  # sets the frequency
        """Sets the frequency of the signal

        Sets the frequency of the signal when the HVPS is in switching mode (SWMODE_SW).\n
        The value returned is the new frequency, taking quantification into account.\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: frequency in Hz between 0.001 and 1000\n
        output: frequency accepted by HVPS"""
        if EMUL:
            z = 1
        else:
            x = constrain(x, 0.001, 1000.0)
            string = 'SF '
            string = string + f"{x:.3f}" + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_f(" + str(x) + ") -> " + str(z)
            print(y)
        self.f = z
        return z

    def q_f(self):  # queries the frequency
        """Queries the switching frequency. The returned value is in Hz."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QF\r')
            z = float(self._readline())
        if DEBUG:
            y = "q_f -> " + str(z)
            print(y)
        self.f = z
        return z

    def s_cycle(self, x):  # sets the number of cycles
        """Sets the number of switching cycles

        Sets the number of switching cycles to perform when the HVPS is in switching mode.
        The maximum value is 65535. A value of 0 means continuous switching (unlimited number of cycles).
        For any value other than 0, the HVPS will change to switching mode 0 (HVPS off, SWMODE_OFF, after the desired
        number of cycles is reached. A new series of switching cycles can be can be initiated by placing the HVPS
        back in switching mode (SWMODE_SW). When you call s_cycle(), the cycle counter is reset to 0.
        Example: s_cycle(1000) to switch 1000 times at the selected frequency and then stop.
        Note that s_cycles configures the HVPS to switch for a limited number of time. If the HVPS is in switching
        mode (SWMODE_SW), then the cycles will start at once. If the HVPS is in off mode (SWMODE_OFF) or in
        DC mode (SWMODE_DC), the cycles will start once the HVPS is put in Switching mode with s_sw_mode(SWMODE_SW).\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: number of cycles (0 to 65535)\n
        output: number of cycles accepted by HVPS"""
        if EMUL:
            z = x
        else:
            x = constrain(x, 0, 65535)
            string = 'SCycle '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_cycle(" + str(x) + ") -> " + str(z)
            print(y)
        self.cycles = z
        return z

    def q_cycle(self):  # queries the number of cycles returns a list. Element 0 is the current cycle number.
        # Element 1 is the total number of cycles in the series.
        """Queries the number of cycles.

        Queries the number of cycles. The returned value is a list z, with z[0] being the current cycle
        number and z[1] the total number of cycles to make. Once the total number of cycles is reached,
        the output is turned off (SWMODE_OFF), and q_cycle() returns [0,XXX] with XXX the number of cycles to make
        (XXX=0 in case of continuous cycles)."""
        if EMUL:
            z = "0/0"
        else:
            self.ser.write(b'QCycle\r')
            z = self._readline()
            z = z.decode("utf-8")
        if DEBUG:
            y = "q_cycle -> " + z
            print(y)
        s = z.split("/")
        self.cycles = s[1]
        self.cycle_n = s[0]
        return s

    # Commands to change the voltage control and switching behaviour
    def s_sw_mode(self, x):  # sets the switching mode
        """Sets the switching mode of the HVPS.

        Sets the switching mode of the HVPS. Four possible values: SWMODE_OFF: HVPS is off (0 V output irrespective
        of voltage set point), SWMODE_DC: the HVPS is in DC mode with a constant output voltage at the desired
        set point, SWMODE_SW: the HVPS is switching at the desired frequency between 0V and Vset, and SWMODE_WFRM:
        the HVPS is in user-defined waveform mode. Setting the switching mode is only effective if the switching
        source is SWSRC_TMR (onboard timer switching).\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: SWMODE_OFF, SWMODE_DC, SWMODE_SW, or SWMODE_WFRM\n
        output: Switching mode set by the HVPS"""
        if EMUL:
            z = x
        else:
            if x > SWMODE_WFRM:
                x = SWMODE_OFF
            string = 'SSwMode '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_sw_mode(" + str(x) + ") -> " + str(z)
            print(y)
        self.swmode = z
        return z

    def q_sw_mode(self):  # queries the switching mode
        """Queries the switching mode of the HVPS.

        Queries the switching mode of the HVPS. Output is either SWMODE_OFF: HVPS is off (0 V output irrespective
        of voltage setpoint), SWMODE_DC the HVPS is in DC mode with a constant output voltage at the desired
        set point, SWMODE_SW: the HVPS is switching at the desired frequency between 0V and Vset, or SWMODE_WFRM:
        the HVPS is in user-defined waveform mode."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QSwMode\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_sw_mode -> " + str(z)
            print(y)
        self.swmode = z
        return z

    def s_sw_src(self, x):  # sets the switching source
        """Sets the source of the switching signal.

        Sets the source of the switching signal. Accepted values are: SWSRC_TMR for onboard switching
        (from internal clock of the board), SWSRC_EXT for external switching via pin 6 (Ext.F) on the board main
        connector, or SWSRC_BTTN for the push button. Example: s_sw_src(SWSRC_EXT) to use the external signal to
        switch the source on/off. Note that this setting is useful only if the jumper on header H2 is set to
        “onboard control”, else the jumper setting defines the source of the switching signal.\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: SWSRC_TMR, or SWSRC_EXT, or SWSRC_BTTN"""
        if EMUL:
            z = x
        else:
            if x > SWSRC_BTTN:
                x = SWSRC_TMR
            string = 'SSwSrc '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_sw_src(" + str(x) + ") -> " + str(z)
            print(y)
        self.swsrc = z
        return z

    def q_sw_src(self):  # queries the switching source
        """Queries the source of the switching signal.

        Queries the source of the switching signal. Output is SWSRC_TMR for onboard switching
        (from internal clock of the board), SWSRC_EXT for external switching via pin 6 (Ext.F) on the board main
        connector, or SWSRC_BTTN for the push button. Example: s_sw_src(SWSRC_EXT) to use the external signal to
        switch the source on/off. Note that this setting is useful only if the jumper on header H2 is set to
        “onboard control”, else the jumper setting defines the source of the switching signal."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QSwSrc\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_sw_src -> " + str(z)
            print(y)
        self.swsrc = z
        return z

    def s_latch_mode(self, x):  # sets the latch mode of the push button
        """Defines the behaviour of the push button

        Defines the behaviour of the push button, when the switching source of the HVPS is set to the push button
        (SWSRC_BTTN, c.f. s_sw_src() command above). Accepted values are 0 and 1: 0 for a push button behaviour
        (i.e. the high voltage is turned on as long as the button is pressed),
        and 1 for a latching switch behaviour (i.e. press once to turn the high voltage on, and press a second time
        to turn it off).\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: 0 or 1\n
        output: latching mode as understood by the HVPS"""
        if EMUL:
            z = x
        else:
            if x > 1:
                x = 1
            string = 'SLatchMode '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_latch_mode(" + str(x) + ") -> " + str(z)
            print(y)
        self.latch = z
        return z

    def q_latch_mode(self):  # queries the latch mode of the push button
        """Queries whether the push button is configured to act as a push button (o) or a latching switch (1)."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QLatchMode\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_latch_mode -> " + str(z)
            print(y)
        self.latch = z
        return z

    def s_v_mode(self, x):  # sets the voltage control mode
        """Sets the voltage control mode

        Sets the voltage control mode (i.e. how is the value of the output voltage controlled):\n
        VMODE_R for internal voltage regulator (regulates the voltage to the value defined with the Vset command).\n
        SMODE_EXT external voltage control (sets the output voltage according to the control voltage applied on pin 5
        (Ext.V) of the main connector of the board. The input voltage range is 0 to 5V.\n
        VMODE_O (that's an O like in open) internal open loop control (on-board regulator disconnected).\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: VMODE_R, VMODE_EXT, VMODE_O\n
        Output: Voltage control mode accepted by the HVPS"""
        if EMUL:
            z = x
        else:
            if x > VMODE_O:
                x = VMODE_R
            string = 'SVMode '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_v_mode(" + str(x) + ") -> " + str(z)
            print(y)
        self.vmode = z
        return z

    def q_v_mode(self):  # queries the switching source
        """Queries the voltage control mode:

        Queries the voltage control mode:\n
        VMODE_R internal voltage regulator, VMODE_EXT external voltage control. VMODE_O internal open loop control
        (on-board regulator disconnected)."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QVMode\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_v_mode -> " + str(z)
            print(y)
        self.vmode = z
        return z

    # ====User Waveform Functions====
    def clear_waveform(self):  # clear the stored waveform
        """Clear the current user-defined waveform

        Clear the current user-defined waveform from the HVPS memory."""
        if EMUL:
            z = 0   # clear waveform returns 0
        else:
            self.ser.write(b'SP X\r')
            z = int(self._readline())
        if DEBUG:
            y = "clear_waveform -> " + str(z)
            print(y)
        return z

    def q_waveform_num_pts(self):  # queries the number of points saved for the waveform
        """Queries how many data point are currently stored in the waveform"""
        if EMUL:
            z = 0   # returns 0 points
        else:
            self.ser.write(b'QPtot\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_waveform_num_pts -> " + str(z)
            print(y)
        return z

    def s_waveform_point(self, x):  # Add a point to the waveform
        """Add a point to the user waveform.

        Add a point to the user waveform. Usage: s_waveform_point(xxx), with xxx between 0 and 255 representing
        0 to 100% of the voltage setpoint.\n
        A new waveform is defined by issuing clear_waveform() (to clear the previous waveform), followed by a series of
        s_waveform_point(xxx) to define the points of the new waveform. The maximal number of allowed points is 255.
        This is a low-level function provided to match the 'SP' command of the HVPS communication protocol. However,
        It is easier to use upload_waveform() to upload a complete waveform to the HVPS in one go, or to use
        upload_std_waveform() to upload some customisable standard waveforms.\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: set point to add to the current waveform. 0-255 representing 0-100% of voltage set point.\n
        output: accepted set point (or -1 if waveform full)"""
        s = [-1, -1]
        x = constrain(x, 0, 255)
        if EMUL:
            z = x
        else:
            num_pts = self.q_waveform_num_pts()   # queries how many points currently saved in memory
            if num_pts < 255:   # can only add a point if less than 255 points currently saved
                string = 'SP '
                string = string + str(x) + '\r'
                string = string.encode()
                self.ser.write(string)
                z = self._readline()
                z = z.decode("utf-8")
                s = z.split(",")
                z = s[1]
            else:
                z = -1
        if DEBUG:
            y = "s_waveform_point(" + str(x) + ") -> " + str(s[0]) + "," + str(s[1])
            print(y)
        return z

    def q_waveform_set_pts(self, x):  # queries the the xth point of the waveform (x starts at 0)
        """queries the waveform set point number x

        queries the waveform set point number x (0<=x<=255) returns a value between 0 and 255 representing 0 to 100%
        of current voltage set point. This is a low-level function provided to match the QP command of the HVPS
        communication protocol. It is easier to use download_waveform_set_pts() to download the whole waveform from
        the HVPS"""
        if EMUL:
            z = x
        else:
            string = 'QP '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "q_waveform_set_point(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_waveform_meas_pts(self, x):  # queries the the measured voltage of the xth point of the
        # waveform (x starts at 0)
        """queries the waveform measured point number x

        queries the waveform measured point number x. Same as q_waveform_set_pts(), but instead of returning the set
        point value, it returns the voltage value read by the SHVPS internally. In order for QR to return meaningful
        values, the SHVPS must have been in Waveform mode (SWMODE_WFRM) for at least one cycle.\n
        Queries the waveform point number x (0<=x<=255). returns a value between 0 and 255 representing 0 to 100%
        of current voltage set point. This is a low-level function provided to match the QR command of the HVPS
        communication protocol. It is easier to use download_waveform_meas_pts() to download the whole waveform from
        the HVPS"""
        if EMUL:
            z = x
        else:
            string = 'QR '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "q_waveform_meas_point(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def upload_waveform(self, x):   # upload a list (x) of waveform set points to the HVPS
        """upload a user-defined waveform to the HVPS

        upload a user-defined waveform to the HVPS. It starts by clearing the current waveform and then it upload
        an new list of points. It also updates the member variable self.waveform_pts with the new list of points.\n
        The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n
        input: x: [p1, p2, p3, ..., pn] where pn is the nth point of the waveform, between 0 and 255, representing 0 to
        100% of the current voltage set point. n is limited to 255.\n
        output: none"""
        self.clear_waveform()   # starts by clearing the waveform
        self.waveform_pts.clear()   # empty the current list of point of the local copy
        if len(x) > 255:    # waveform limited to 255 points
            x = x[:255]
        for point in x:
            point = constrain(point, 0, 255)
            self.waveform_pts.append(self.s_waveform_point(point))      # upload the point and add it to the local copy

    def download_waveform_set_pts(self):   # download the waveform set points stored in the HVPS
        """Download the current waveform from the HVPS

        Download the current waveform from the HVPS. The output is a list of set points between 0 and 255 representing
        0 to 100% of the voltage set point. the downloaded points are stored in the member list waveform_pts"""
        self.waveform_pts.clear()   # empty the current list of point of the local copy
        num_points = self.q_waveform_num_pts()
        for i in range(num_points):
            self.waveform_pts.append(self.q_waveform_set_pts(i))      # download the point +add it to the local copy

    def download_waveform_meas_pts(self):   # download the waveform set points stored in the HVPS
        """Download the measured waveform (last performed cycle) from the HVPS

        Download the current waveform from the HVPS. The output is a list of set points between 0 and 255 representing
        0 to 100% of the voltage set point. the SHVPS must have been in Waveform mode (SWMODE_WFRM) for at least one
        cycle to obtain meaningful values. member list waveform_meas"""
        self.waveform_meas.clear()   # empty the current list of point of the local copy
        num_points = self.q_waveform_num_pts()
        for i in range(num_points):
            self.waveform_meas.append(self.q_waveform_meas_pts(i))      # download the point +add it to the local copy

    def upload_std_waveform(self, func=FUNC_SINE, sr=False, n=100,  b=0.15):
        """Upload a customisable standard waveform to the HVPS

        inputs:\n
        func: FUNC_SINE (a sine wave with an offset to be between 0 and Voltage set point), FUNC_TRI
        (a triangle function), FUNC_TRAP (a Trapezoid function), FUNC_CSTM (a custom waveform. Points (a maximum number
        of 255 points) should be defined in a file named waveform.txt located alongside this library. There should be
        1 point per line, each point between 0 and 1, representing 0 to 100% of the voltage set point)\n
        sr: (square root) True or False. In case the HVPS is used to drive dielectric elastomer actuators, there is
        a quadratic relationship between voltage and actuation strain. True: a square root correction is applied so
        that the actuation strain will roughly have the chosen profile. False: No correction applied/n
        n: number of point in the waveform. Max is 255. It depends on the frequency at which the waveform will be
        produced, For a 1Hz signal, 100 points are adequate. Reduce the number of points for higher frequencies
        b: This applies only for the FUNC_TRAP function and defines the percentage of the period that the raising
        (and falling) edge should take. The value should be smaller than 0.5 (at which point the waveform becomes a
        triangle).\n
         The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
        save() command enables to save this parameter in memory\n"""
        pts = []
        n = constrain(n, 1, 255)     # n must be between 1 and 255 points
        b = constrain(b, 0, 0.5)      # b must be between 0 and 0.5
        if sr:      # if we want the square root of the signal (because of the quadratic relationship between voltage
            #  and strain for DEAs)
            power = 0.5
        else:
            power = 1.0

        if func == FUNC_CSTM:   # custom user waveform
            try:
                fp = open('./waveform.txt', 'r')
            except FileNotFoundError:
                if DEBUG:
                    print("Custom waveform must be in ./waveform.txt, but file is not found")
                fp = 0
            if fp:
                list_of_points = fp.readlines()
                for point in list_of_points:
                    try:
                        point = int(255*(float(point) ** power))
                    except ValueError:
                        point = 0
                        if DEBUG:
                            print("Error when reading point for custom waveform. Each line in the file ./waveform.txt "
                                  "must contain a single floating point number")
                    point = constrain(point, 0, 255)    # points should be between 0 and 255
                    pts.append(point)
                fp.close()
        else:   # if other standard functions are chosen
            for i in range(n):
                p = 0       # p is between 0 to 1 representing percentage of voltage set point
                if func == FUNC_SINE:   # Sine + offset waveform
                    p = (0.5+0.5*sin(2*pi/n*i)) ** power
                elif func == FUNC_TRAP:  # trapeze waveform
                    if i <= b*n:    # the ramp up of the trapeze
                        p = (i/b/n) ** power
                    elif i <= n/2:  # holding time
                        p = 1
                    elif i <= (n/2 + b*n):    # ramp down
                        p = (1-(i - n/2) / b / n) ** power
                    else:
                        p = 0
                elif func == FUNC_TRI:
                    if i <= n/2:    # Raising edge
                        p = (2/n*i) ** power
                    else:
                        p = (1 - 2 / n * (i - n / 2)) ** power
                p = int(p*255)
                pts.append(p)

        self.upload_waveform(pts)       # uploads the waveform to the HVPS

    # ====Trigger / Strobe pulse functions====
    def s_st_mode(self, x):  # sets the strobe mode
        """Sets the strobe mode.

        Sets the strobe mode. Usage: s_st_mode(X), with X being\n
        STMODE_OFF: trigger/strobe pulse off: no signal is generated.\n
        STMODE_FIXED: fixed position: a trigger/strobe signal is generated at a fixed position, with a fixed duration.
        By default, the pulse is synchronized with the HV switching signal, but the position and duration of the pulse
        can be defined by the user (see next s_st_pos()).\n
        STMODE_SWEEP: sweep mode: the position of the trigger/strobe pulse is shifted along the HV signal at a
        user-defined speed. The HVPS must be in switching mode (SWMODE_SW) for the trigger pulse to be generated.
        The trigger pulse is generated on pin T of the multi-purpose 10-pins header on the HVPS board.\n
        input: x: STMODE_OFF or STMODE_FIXED, or STMODE_SWEEP\n
        output: strobe mode as understood by HVPS"""
        if EMUL:
            z = x
        else:
            if x > STMODE_SWEEP:
                x = STMODE_OFF
            string = 'SStMode '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_st_mode(" + str(x) + ") -> " + str(z)
            print(y)
        self.stmode = z
        return z

    def q_st_mode(self):  # queries the strobe mode
        """Queries the current strobe mode.

        Queries the current strobe mode. 0: Off, 1: Fixed position, 2: Sweep mode.\n
        STMODE_OFF: trigger/strobe pulse off: no signal is generated.\n
        STMODE_FIXED: fixed position: a trigger/strobe signal is generated at a fixed position, with a fixed duration.
        By default, the pulse is synchronized with the HV switching signal, but the position and duration of the pulse
        can be defined by the user (see next s_st_pos()).\n
        STMODE_SWEEP: sweep mode: the position of the trigger/strobe pulse is shifted along the HV signal at a
        user-defined speed."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QStMode\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_st_mode -> " + str(z)
            print(y)
        self.stmode = z
        return z

    def s_st_pos(self, x):  # sets the position of the strobe pulse
        """Sets the position of the rising edge of the strobe pulse with respect to the HV output.

        Sets the position of the rising edge of the strobe pulse with respect to the HV output.
        Usage: s_st_pos(xxx), with xxx a value between 0 and 255, representing the position of the rising edge of the
        trigger pulse as a fraction of the period T of the HV signal. A value of 0 means that the rising edge of the
        trigger pulse is coincident with the rising edge of the HV output.\n
        A value of 127 means that the rising edge of the trigger pulse is coincident with the falling edge of the HV
        output. The position of the pulse is only used when the strobe pulse mode is STMODE_FIXED (fixed position).\n
        input: x: position of the trigger pulse (0 to 255)\n
        output: position of the trigger pulse accepted by the HVPS"""
        if EMUL:
            z = x
        else:
            x = constrain(x, 0, 255)
            string = 'SStPos '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_st_pos(" + str(x) + ") -> " + str(z)
            print(y)
        self.stpos = z
        return z

    def q_st_pos(self):  # queries the the position of the strobe pulse
        """Queries the current position of the rising edge of the strobe/trigger pulse.

        Queries the current position of the rising edge of the strobe/trigger pulse. The returned value is
        between 0 and 255, and represents a fraction of the period of the HV output, with 0
        being the rising edge of the HV signal."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QStPos\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_st_pos -> " + str(z)
            print(y)
        self.stpos = z
        return z

    def s_st_dur(self, x):  # sets the duration of the strobe pulse
        """Sets the duration of the strobe/trigger pulse as a fraction of the period of the HV output.

        Sets the duration of the strobe/trigger pulse as a fraction of the period of the HV output.
        Usage: s_st_dur(xxx), with xxx being the fraction of the period of the HV output during which the
        strobe/trigger pulse must remain high
        (0: constantly off, 127: on during 50% of the period, and 255: constantly on).\n
        The duration of the pulse is used in strobe mode ST_MODE_FIXED (fixed position) and STMODE_SWEEP (sweep mode)\n
        input: x: duration of the strobe pulse (0-255, representing 0 to 100% duty cycle)\n
        output: pulse duration accepted by the HVPS"""
        if EMUL:
            z = x
        else:
            x = constrain(x, 0, 255)
            string = 'SStDur '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_st_dur(" + str(x) + ") -> " + str(z)
            print(y)
        self.stdur = z
        return z

    def q_st_dur(self):  # queries the the duration of the strobe pulse
        """Queries the current duration of the strobe/trigger pulse.

        Queries the current duration of the strobe/trigger pulse. Returns a value between 0 and 255 representing
        the fraction of the period of the HV signal during which the strobe/trigger signal remains high
        (0: constantly off, 127: on during 50% of the period, and 255: constantly on)."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QStDur\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_st_dur -> " + str(z)
            print(y)
        self.stdur = z
        return z

    def s_st_sweep(self, x):  # sets the sweep period (im ms) when in sweep mode
        """sets the sweep time in ms.

        sets the sweep time in ms. Usage s_st_sweep(xxx), where xxx is the time  (in ms) that it takes to sweep the
        strobe/trigger pulse over a complete period of the HV output. This parameter is only used in strobe mode
        STMODE_SWEEP (sweep mode). If the pulse is used to drive a strobe LED, the sweep time defines the apparent
        period of the motion. Logically, the period set with s_st_sweep much be considerably longer than the period
        of the HV signal.\n
        input: x: period of the sweep duration in ms\n
        output: sweep duration accepted by HVPS"""
        if EMUL:
            z = x
        else:
            x = constrain(x, 0, 65535)
            string = 'SStSw '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_st_sweep(" + str(x) + ") -> " + str(z)
            print(y)
        self.stsweep = z
        return z

    # Miscellaneous functions
    def save(self):  # save current HVPS parameters into the memory
        """save current HVPS parameters into the memory

        This command saves the following parameters in EEPROM: Voltage setpoint, Frequency, Frequency divider,
        Source of switching signal (external, internal, button), Switching mode (off, DC output, switching output),
        Voltage mode (internal voltage regulator or following external signal), and number of switching cycles.
        It also saves the user-defined waveform into the EEPROM. This is useful so that it is not necessary to
        reconfigure the HVPS for the desired behavior every time it is powered up, for example when the HVPS is
        meant to be used without computer (when connected to a demo device). Note that when the HVPS is used with a
        computer, it is safer to save a voltage setpoint of 0V (and/or a switching mode 0 (box off)) so as to be sure
        that no high voltage is present at the output when the board is powered up (and before the computer has the
        time to initialise it to 0V output)."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'Save\r')
            z = int(self._readline())
        if DEBUG:
            y = "save -> " + str(z)
            print(y)
        return z

    def q_mem(self):  # queries the content of the memory
        """queries the content of the memory

        Queries the content of the memory (when the box is powered up, it will use the settings stored in memory,
        which allows using the board without a computer).
        This commands returns a string of parameters separated by commas: Voltage, Frequency, Switching source,
        Switching mode, Voltage mode, Regulator gain P, Regulator gain I, Regulator gain D ,
        Voltage Calibration factor 0, Voltage Calibration factor 1, Voltage Calibration factor 2,
        number of switching cycles, button latching mode.\n
        input: none\n
        output: a HVPSMem object with the values of the different parameters."""
        mem = HVPSMem()
        if not EMUL:
            self.ser.write(b'QMem\r')
            z = self._readline()
            z = z.decode("utf-8")
            s = z.split(",")
            mem.vset = int(s[0])
            mem.f = float(s[1])
            mem.swsrc = int(s[2])
            mem.swmode = int(s[3])
            mem.vmode = int(s[4])
            mem.kp = float(s[5])
            mem.ki = float(s[6])
            mem.kd = float(s[7])
            mem.c0 = float(s[8])
            mem.c1 = float(s[9])
            mem.c2 = float(s[10])
            mem.cycles = int(s[11])
            mem.latch = int(s[12])
        else:
            z = 'empty memory'
        if DEBUG:
            y = "q_mem -> " + z
            print(y)
        return mem

    def q_ver(self):  # queries the firmware version
        """returns the current version of the firmware running on the board."""
        if EMUL:
            z = 7
        else:
            self.ser.write(b'QVer\r')
            s = self._readline()  # returns a string in the form of "slave X" need to get the value of X
            s = s.decode("utf-8")
            z1 = s.split(" ")
            z = int(z1[1])
        if DEBUG:
            y = "q_ver -> " + str(z)
            print(y)
        return z

    def q_jack(self):  # queries whether power Jack is plugged in.
        """returns 1 if the power adapter is plugged in the Jack socket J1, and 0 if it is not."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QJack\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_jack -> " + str(z)
            print(y)
        return z

    # Configuration functions
    def conf(self, x):  # performs initial configuration of the board
        """Performs the initial configuration of a board

        Performs the initial configuration of a board (instead of using commands below separately).
        Usage: conf(xxx), where XXX is the voltage rating of one of the 5 standard configurations
        (i.e. 5000, 3000, 2000, 1200, and 500). Initialises Vmax to the value of XXX, and C0, C1, C2, Kp, Ki, and Kd
        to the default values for each voltage rating.\n
        input: x: board voltage rating (5000, 3000, 2000, 1200, or 500)
        output: 1 in case of success, 0 in case of failure"""
        if EMUL:
            z = 1  # this emulate success
        else:
            if x == 5000 or x == 3000 or x == 2000 or x == 1200 or x == 500:
                string = 'Conf '
                string = string + str(x) + '\r'
                string = string.encode()
                self.ser.write(string)
                z = int(self._readline()) > 0
                self._initialise()  # initialises the board now that it has been configured
            else:
                z = 0  # 0 indicates failure (in that case entered voltage not one of the HVPS std voltages
        if DEBUG:
            y = "conf(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def s_i2c(self, x):  # sets the I2C address
        """sets the I2C address of the board. Only useful if board to be used in multichannel configuration

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n
        input: x: I2C address to assign"""
        if EMUL:
            z = 12  # random returned i2c value in case we emulate the presence of a board
        else:
            x = constrain(x, 0, 127)
            string = 'SI2C '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_I2C(" + str(x) + ") -> " + str(z)
            print(y)
        self.i2c = z
        return z

    def q_i2c(self):  # queries the i2c address of the board
        """queries the I2C address of the board."""
        if EMUL:
            z = 12
        else:
            self.ser.write(b'QI2C\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_I2C -> " + str(z)
            print(y)
        self.i2c = z
        return z

    def s_vmax(self, x):  # sets the maximum voltage rating of the board
        """sets the voltage rating of the SHVPS.

        sets the voltage rating of the SHVPS. Must match the EMCO DC/DC converter rating.\n
        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n
        input: x:voltage rating of HVPS in Volt"""
        if EMUL:
            z = x
        else:
            x = constrain(x, 0, 10000)
            string = 'SVmax '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_vmax(" + str(x) + ") -> " + str(z)
            print(y)
        self.vmax = z
        return z

    def q_vmax(self):  # queries the voltage rating of the board
        """Queries the maximal voltage of the board. The returned value is in volts."""
        if EMUL:
            z = 5000
        else:
            self.ser.write(b'QVmax\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_vmax -> " + str(z)
            print(y)
        self.vmax = z
        return z

    def s_c0(self, x):  # sets the calibration constant c0
        """sets the calibration constant c0

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
        if EMUL:
            z = x
        else:
            string = 'SC0 '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_c0(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_c0(self):  # queries the calibration constant c0
        """queries the calibration constant c0"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QC0\r')
            z = float(self._readline())
        if DEBUG:
            y = "q_c0 -> " + str(z)
            print(y)
        return z

    def s_c1(self, x):  # sets the calibration constant c1
        """sets the calibration constant c1

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
        if EMUL:
            z = x
        else:
            string = 'SC1 '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_c1(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_c1(self):  # queries the calibration constant c1
        """queries the calibration constant c1"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QC1\r')
            z = float(self._readline())
        if DEBUG:
            y = "q_c1 -> " + str(z)
            print(y)
        return z

    def s_c2(self, x):  # sets the calibration constant c2
        """sets the calibration constant c2

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
        if EMUL:
            z = x
        else:
            string = 'SC2 '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_c2(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_c2(self):  # queries the calibration constant c2
        """queries the calibration constant c2"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QC2\r')
            z = float(self._readline())
        if DEBUG:
            y = "q_c2 -> " + str(z)
            print(y)
        return z

    def s_kp(self, x):  # sets the PID gain Kp
        """sets parameters Kp of the PID regulator.

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
        if EMUL:
            z = x
        else:
            string = 'SKp '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_kp(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_kp(self):  # queries the PID gain Kp
        """Query the value of parameter Kp of the PID regulator"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QKp\r')
            z = float(self._readline())

        if DEBUG:
            y = "q_kp -> " + str(z)
            print(y)
        return z

    def s_ki(self, x):  # sets the PID gain Ki
        """""sets parameters Ki of the PID regulator.

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
        if EMUL:
            z = x
        else:
            string = 'SKi '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_ki(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_ki(self):  # queries the PID gain Ki
        """Query the value of parameter Ki of the PID regulator"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QKi\r')
            z = float(self._readline())
        if DEBUG:
            y = "q_ki -> " + str(z)
            print(y)
        return z

    def s_kd(self, x):  # sets the PID gain Kd
        """sets parameters Kd of the PID regulator.

        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
        if EMUL:
            z = x
        else:
            string = 'SKd '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = float(self._readline())
        if DEBUG:
            y = "s_kd(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_kd(self):  # queries the PID gain Kd
        """Query the value of parameter Kd of the PID regulator"""
        if EMUL:
            z = 0
        else:
            self.ser.write(b'QKd\r')
            z = float(self._readline())
        if DEBUG:
            y = "q_kp -> " + str(z)
            print(y)
        return z

    def s_name(self, x):  # set the name of the HVPS
        """Sets the name of the HVPS.

        Sets the name of the HVPS. 20 characters maximum\n
        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n
        input: x=Name of the HVPS
        output: name accepted by HVPS"""
        if EMUL:
            z = x
        elif len(x) < 21:
            string = 'SName '
            string = string + x + '\r'
            string = string.encode()
            self.ser.write(string)
            z = self._readline()
        else:
            z = 'too long'
        if DEBUG:
            y = "s_kd(" + str(x) + ") -> " + str(z)
            print(y)
        return z

    def q_name(self):  # queries the name of the board
        """queries the name of the board"""
        if EMUL:
            z = "Emulator"
        else:
            self.ser.write(b'QName\r')
            z = self._readline()
        if DEBUG:
            y = "q_name -> " + str(z)
            print(y)
        self.name = z
        return z

    def q_type(self):  # queries the type (master (multi-channel), slave (single channel))
        """queries the type (master (multi-channel), slave (single channel))"""
        if EMUL:
            z = "slave"
        else:
            self.ser.write(b'QVer\r')
            s = self._readline()  # returns a string in the form of "slave X" need to get slave
            s = s.decode("utf-8")
            z1 = s.split(" ")
            z = z1[0]
        if DEBUG:
            y = "q_ver -> " + str(z)
            print(y)
        return z

    def s_power_jack(self, x):  # sets the configuration of the HVPS (whether power comes from power jack)
        """Defines if power if provided by the power jack.

        Defines if power if provided by the power jack, s_power_jack(1) (default), or through 5V pin, s_power_jack(0).
        As the default is power jack 1, You only need the use this command with SPowJack 0 if you wish to use the HVPS
        in the standalone configuration with touchscreen and battery, or in the multichannel configuration.\n
        <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n
        input: x: 0 or 1
        output: power jack mode accepted by HVPS"""
        if EMUL:
            z = x
        else:
            if x > 1:
                x = 1
            string = 'SPowJack '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline())
        if DEBUG:
            y = "s_power_jack(" + str(x) + ") -> " + str(z)
            print(y)
        self.config = not z     # updates the configuration status of the HVPS
        return z

    def q_power_jack(self):  # queries whether power is supposed to come from Power Jack.
        # (0 if in touchscreen + battery configuration)
        """Queries the power jack setting.

        Queries the power jack setting. 1 when HVPS expects to receive power from power jack, and 0 when power
        comes from 5 V pin."""
        if EMUL:
            z = 1
        else:
            self.ser.write(b'QPowJack\r')
            z = int(self._readline())
        if DEBUG:
            y = "q_power_jack -> " + str(z)
            print(y)
        return z

Methods

def clear_waveform(self)

Clear the current user-defined waveform

Clear the current user-defined waveform from the HVPS memory.

Expand source code
def clear_waveform(self):  # clear the stored waveform
    """Clear the current user-defined waveform

    Clear the current user-defined waveform from the HVPS memory."""
    if EMUL:
        z = 0   # clear waveform returns 0
    else:
        self.ser.write(b'SP X\r')
        z = int(self._readline())
    if DEBUG:
        y = "clear_waveform -> " + str(z)
        print(y)
    return z
def close(self)

Closes the connection to the HVPS. Sets voltage to 0 if board was connected

Expand source code
def close(self):  # closes connection with the HVPS
    """Closes the connection to the HVPS. Sets voltage to 0 if board was connected"""
    if not EMUL:
        if self.ser.is_open:
            if not (self.err & ERR_COM):  # if connected board is not running the firmware, do not send command
                self.s_vset(0)  # set the voltage to 0 as a safety measure
            self.ser.close()

    if DEBUG:
        print("Serial port closed")
def conf(self, x)

Performs the initial configuration of a board

Performs the initial configuration of a board (instead of using commands below separately). Usage: conf(xxx), where XXX is the voltage rating of one of the 5 standard configurations (i.e. 5000, 3000, 2000, 1200, and 500). Initialises Vmax to the value of XXX, and C0, C1, C2, Kp, Ki, and Kd to the default values for each voltage rating.

input: x: board voltage rating (5000, 3000, 2000, 1200, or 500) output: 1 in case of success, 0 in case of failure

Expand source code
def conf(self, x):  # performs initial configuration of the board
    """Performs the initial configuration of a board

    Performs the initial configuration of a board (instead of using commands below separately).
    Usage: conf(xxx), where XXX is the voltage rating of one of the 5 standard configurations
    (i.e. 5000, 3000, 2000, 1200, and 500). Initialises Vmax to the value of XXX, and C0, C1, C2, Kp, Ki, and Kd
    to the default values for each voltage rating.\n
    input: x: board voltage rating (5000, 3000, 2000, 1200, or 500)
    output: 1 in case of success, 0 in case of failure"""
    if EMUL:
        z = 1  # this emulate success
    else:
        if x == 5000 or x == 3000 or x == 2000 or x == 1200 or x == 500:
            string = 'Conf '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = int(self._readline()) > 0
            self._initialise()  # initialises the board now that it has been configured
        else:
            z = 0  # 0 indicates failure (in that case entered voltage not one of the HVPS std voltages
    if DEBUG:
        y = "conf(" + str(x) + ") -> " + str(z)
        print(y)
    return z
def download_waveform_meas_pts(self)

Download the measured waveform (last performed cycle) from the HVPS

Download the current waveform from the HVPS. The output is a list of set points between 0 and 255 representing 0 to 100% of the voltage set point. the SHVPS must have been in Waveform mode (SWMODE_WFRM) for at least one cycle to obtain meaningful values. member list waveform_meas

Expand source code
def download_waveform_meas_pts(self):   # download the waveform set points stored in the HVPS
    """Download the measured waveform (last performed cycle) from the HVPS

    Download the current waveform from the HVPS. The output is a list of set points between 0 and 255 representing
    0 to 100% of the voltage set point. the SHVPS must have been in Waveform mode (SWMODE_WFRM) for at least one
    cycle to obtain meaningful values. member list waveform_meas"""
    self.waveform_meas.clear()   # empty the current list of point of the local copy
    num_points = self.q_waveform_num_pts()
    for i in range(num_points):
        self.waveform_meas.append(self.q_waveform_meas_pts(i))      # download the point +add it to the local copy
def download_waveform_set_pts(self)

Download the current waveform from the HVPS

Download the current waveform from the HVPS. The output is a list of set points between 0 and 255 representing 0 to 100% of the voltage set point. the downloaded points are stored in the member list waveform_pts

Expand source code
def download_waveform_set_pts(self):   # download the waveform set points stored in the HVPS
    """Download the current waveform from the HVPS

    Download the current waveform from the HVPS. The output is a list of set points between 0 and 255 representing
    0 to 100% of the voltage set point. the downloaded points are stored in the member list waveform_pts"""
    self.waveform_pts.clear()   # empty the current list of point of the local copy
    num_points = self.q_waveform_num_pts()
    for i in range(num_points):
        self.waveform_pts.append(self.q_waveform_set_pts(i))      # download the point +add it to the local copy
def q_c0(self)

queries the calibration constant c0

Expand source code
def q_c0(self):  # queries the calibration constant c0
    """queries the calibration constant c0"""
    if EMUL:
        z = 0
    else:
        self.ser.write(b'QC0\r')
        z = float(self._readline())
    if DEBUG:
        y = "q_c0 -> " + str(z)
        print(y)
    return z
def q_c1(self)

queries the calibration constant c1

Expand source code
def q_c1(self):  # queries the calibration constant c1
    """queries the calibration constant c1"""
    if EMUL:
        z = 0
    else:
        self.ser.write(b'QC1\r')
        z = float(self._readline())
    if DEBUG:
        y = "q_c1 -> " + str(z)
        print(y)
    return z
def q_c2(self)

queries the calibration constant c2

Expand source code
def q_c2(self):  # queries the calibration constant c2
    """queries the calibration constant c2"""
    if EMUL:
        z = 0
    else:
        self.ser.write(b'QC2\r')
        z = float(self._readline())
    if DEBUG:
        y = "q_c2 -> " + str(z)
        print(y)
    return z
def q_cycle(self)

Queries the number of cycles.

Queries the number of cycles. The returned value is a list z, with z[0] being the current cycle number and z[1] the total number of cycles to make. Once the total number of cycles is reached, the output is turned off (SWMODE_OFF), and q_cycle() returns [0,XXX] with XXX the number of cycles to make (XXX=0 in case of continuous cycles).

Expand source code
def q_cycle(self):  # queries the number of cycles returns a list. Element 0 is the current cycle number.
    # Element 1 is the total number of cycles in the series.
    """Queries the number of cycles.

    Queries the number of cycles. The returned value is a list z, with z[0] being the current cycle
    number and z[1] the total number of cycles to make. Once the total number of cycles is reached,
    the output is turned off (SWMODE_OFF), and q_cycle() returns [0,XXX] with XXX the number of cycles to make
    (XXX=0 in case of continuous cycles)."""
    if EMUL:
        z = "0/0"
    else:
        self.ser.write(b'QCycle\r')
        z = self._readline()
        z = z.decode("utf-8")
    if DEBUG:
        y = "q_cycle -> " + z
        print(y)
    s = z.split("/")
    self.cycles = s[1]
    self.cycle_n = s[0]
    return s
def q_f(self)

Queries the switching frequency. The returned value is in Hz.

Expand source code
def q_f(self):  # queries the frequency
    """Queries the switching frequency. The returned value is in Hz."""
    if EMUL:
        z = 1
    else:
        self.ser.write(b'QF\r')
        z = float(self._readline())
    if DEBUG:
        y = "q_f -> " + str(z)
        print(y)
    self.f = z
    return z
def q_i2c(self)

queries the I2C address of the board.

Expand source code
def q_i2c(self):  # queries the i2c address of the board
    """queries the I2C address of the board."""
    if EMUL:
        z = 12
    else:
        self.ser.write(b'QI2C\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_I2C -> " + str(z)
        print(y)
    self.i2c = z
    return z
def q_jack(self)

returns 1 if the power adapter is plugged in the Jack socket J1, and 0 if it is not.

Expand source code
def q_jack(self):  # queries whether power Jack is plugged in.
    """returns 1 if the power adapter is plugged in the Jack socket J1, and 0 if it is not."""
    if EMUL:
        z = 1
    else:
        self.ser.write(b'QJack\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_jack -> " + str(z)
        print(y)
    return z
def q_kd(self)

Query the value of parameter Kd of the PID regulator

Expand source code
def q_kd(self):  # queries the PID gain Kd
    """Query the value of parameter Kd of the PID regulator"""
    if EMUL:
        z = 0
    else:
        self.ser.write(b'QKd\r')
        z = float(self._readline())
    if DEBUG:
        y = "q_kp -> " + str(z)
        print(y)
    return z
def q_ki(self)

Query the value of parameter Ki of the PID regulator

Expand source code
def q_ki(self):  # queries the PID gain Ki
    """Query the value of parameter Ki of the PID regulator"""
    if EMUL:
        z = 0
    else:
        self.ser.write(b'QKi\r')
        z = float(self._readline())
    if DEBUG:
        y = "q_ki -> " + str(z)
        print(y)
    return z
def q_kp(self)

Query the value of parameter Kp of the PID regulator

Expand source code
def q_kp(self):  # queries the PID gain Kp
    """Query the value of parameter Kp of the PID regulator"""
    if EMUL:
        z = 0
    else:
        self.ser.write(b'QKp\r')
        z = float(self._readline())

    if DEBUG:
        y = "q_kp -> " + str(z)
        print(y)
    return z
def q_latch_mode(self)

Queries whether the push button is configured to act as a push button (o) or a latching switch (1).

Expand source code
def q_latch_mode(self):  # queries the latch mode of the push button
    """Queries whether the push button is configured to act as a push button (o) or a latching switch (1)."""
    if EMUL:
        z = 1
    else:
        self.ser.write(b'QLatchMode\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_latch_mode -> " + str(z)
        print(y)
    self.latch = z
    return z
def q_mem(self)

queries the content of the memory

Queries the content of the memory (when the box is powered up, it will use the settings stored in memory, which allows using the board without a computer). This commands returns a string of parameters separated by commas: Voltage, Frequency, Switching source, Switching mode, Voltage mode, Regulator gain P, Regulator gain I, Regulator gain D , Voltage Calibration factor 0, Voltage Calibration factor 1, Voltage Calibration factor 2, number of switching cycles, button latching mode.

input: none

output: a HVPSMem object with the values of the different parameters.

Expand source code
def q_mem(self):  # queries the content of the memory
    """queries the content of the memory

    Queries the content of the memory (when the box is powered up, it will use the settings stored in memory,
    which allows using the board without a computer).
    This commands returns a string of parameters separated by commas: Voltage, Frequency, Switching source,
    Switching mode, Voltage mode, Regulator gain P, Regulator gain I, Regulator gain D ,
    Voltage Calibration factor 0, Voltage Calibration factor 1, Voltage Calibration factor 2,
    number of switching cycles, button latching mode.\n
    input: none\n
    output: a HVPSMem object with the values of the different parameters."""
    mem = HVPSMem()
    if not EMUL:
        self.ser.write(b'QMem\r')
        z = self._readline()
        z = z.decode("utf-8")
        s = z.split(",")
        mem.vset = int(s[0])
        mem.f = float(s[1])
        mem.swsrc = int(s[2])
        mem.swmode = int(s[3])
        mem.vmode = int(s[4])
        mem.kp = float(s[5])
        mem.ki = float(s[6])
        mem.kd = float(s[7])
        mem.c0 = float(s[8])
        mem.c1 = float(s[9])
        mem.c2 = float(s[10])
        mem.cycles = int(s[11])
        mem.latch = int(s[12])
    else:
        z = 'empty memory'
    if DEBUG:
        y = "q_mem -> " + z
        print(y)
    return mem
def q_name(self)

queries the name of the board

Expand source code
def q_name(self):  # queries the name of the board
    """queries the name of the board"""
    if EMUL:
        z = "Emulator"
    else:
        self.ser.write(b'QName\r')
        z = self._readline()
    if DEBUG:
        y = "q_name -> " + str(z)
        print(y)
    self.name = z
    return z
def q_power_jack(self)

Queries the power jack setting.

Queries the power jack setting. 1 when HVPS expects to receive power from power jack, and 0 when power comes from 5 V pin.

Expand source code
def q_power_jack(self):  # queries whether power is supposed to come from Power Jack.
    # (0 if in touchscreen + battery configuration)
    """Queries the power jack setting.

    Queries the power jack setting. 1 when HVPS expects to receive power from power jack, and 0 when power
    comes from 5 V pin."""
    if EMUL:
        z = 1
    else:
        self.ser.write(b'QPowJack\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_power_jack -> " + str(z)
        print(y)
    return z
def q_pwm(self)

Queries the current HVPS set point as a raw PWM value. returns value between 0 and 1023

Expand source code
def q_pwm(self):  # queries the pwm setting
    """Queries the current HVPS set point as a raw PWM value. returns value between 0 and 1023"""
    if EMUL:
        z = 0
    else:
        self.ser.write(b'QPWM\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_pwm -> " + str(z)
        print(y)
    return z
def q_st_dur(self)

Queries the current duration of the strobe/trigger pulse.

Queries the current duration of the strobe/trigger pulse. Returns a value between 0 and 255 representing the fraction of the period of the HV signal during which the strobe/trigger signal remains high (0: constantly off, 127: on during 50% of the period, and 255: constantly on).

Expand source code
def q_st_dur(self):  # queries the the duration of the strobe pulse
    """Queries the current duration of the strobe/trigger pulse.

    Queries the current duration of the strobe/trigger pulse. Returns a value between 0 and 255 representing
    the fraction of the period of the HV signal during which the strobe/trigger signal remains high
    (0: constantly off, 127: on during 50% of the period, and 255: constantly on)."""
    if EMUL:
        z = 1
    else:
        self.ser.write(b'QStDur\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_st_dur -> " + str(z)
        print(y)
    self.stdur = z
    return z
def q_st_mode(self)

Queries the current strobe mode.

Queries the current strobe mode. 0: Off, 1: Fixed position, 2: Sweep mode.

STMODE_OFF: trigger/strobe pulse off: no signal is generated.

STMODE_FIXED: fixed position: a trigger/strobe signal is generated at a fixed position, with a fixed duration. By default, the pulse is synchronized with the HV switching signal, but the position and duration of the pulse can be defined by the user (see next s_st_pos()).

STMODE_SWEEP: sweep mode: the position of the trigger/strobe pulse is shifted along the HV signal at a user-defined speed.

Expand source code
def q_st_mode(self):  # queries the strobe mode
    """Queries the current strobe mode.

    Queries the current strobe mode. 0: Off, 1: Fixed position, 2: Sweep mode.\n
    STMODE_OFF: trigger/strobe pulse off: no signal is generated.\n
    STMODE_FIXED: fixed position: a trigger/strobe signal is generated at a fixed position, with a fixed duration.
    By default, the pulse is synchronized with the HV switching signal, but the position and duration of the pulse
    can be defined by the user (see next s_st_pos()).\n
    STMODE_SWEEP: sweep mode: the position of the trigger/strobe pulse is shifted along the HV signal at a
    user-defined speed."""
    if EMUL:
        z = 1
    else:
        self.ser.write(b'QStMode\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_st_mode -> " + str(z)
        print(y)
    self.stmode = z
    return z
def q_st_pos(self)

Queries the current position of the rising edge of the strobe/trigger pulse.

Queries the current position of the rising edge of the strobe/trigger pulse. The returned value is between 0 and 255, and represents a fraction of the period of the HV output, with 0 being the rising edge of the HV signal.

Expand source code
def q_st_pos(self):  # queries the the position of the strobe pulse
    """Queries the current position of the rising edge of the strobe/trigger pulse.

    Queries the current position of the rising edge of the strobe/trigger pulse. The returned value is
    between 0 and 255, and represents a fraction of the period of the HV output, with 0
    being the rising edge of the HV signal."""
    if EMUL:
        z = 1
    else:
        self.ser.write(b'QStPos\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_st_pos -> " + str(z)
        print(y)
    self.stpos = z
    return z
def q_sw_mode(self)

Queries the switching mode of the HVPS.

Queries the switching mode of the HVPS. Output is either SWMODE_OFF: HVPS is off (0 V output irrespective of voltage setpoint), SWMODE_DC the HVPS is in DC mode with a constant output voltage at the desired set point, SWMODE_SW: the HVPS is switching at the desired frequency between 0V and Vset, or SWMODE_WFRM: the HVPS is in user-defined waveform mode.

Expand source code
def q_sw_mode(self):  # queries the switching mode
    """Queries the switching mode of the HVPS.

    Queries the switching mode of the HVPS. Output is either SWMODE_OFF: HVPS is off (0 V output irrespective
    of voltage setpoint), SWMODE_DC the HVPS is in DC mode with a constant output voltage at the desired
    set point, SWMODE_SW: the HVPS is switching at the desired frequency between 0V and Vset, or SWMODE_WFRM:
    the HVPS is in user-defined waveform mode."""
    if EMUL:
        z = 1
    else:
        self.ser.write(b'QSwMode\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_sw_mode -> " + str(z)
        print(y)
    self.swmode = z
    return z
def q_sw_src(self)

Queries the source of the switching signal.

Queries the source of the switching signal. Output is SWSRC_TMR for onboard switching (from internal clock of the board), SWSRC_EXT for external switching via pin 6 (Ext.F) on the board main connector, or SWSRC_BTTN for the push button. Example: s_sw_src(SWSRC_EXT) to use the external signal to switch the source on/off. Note that this setting is useful only if the jumper on header H2 is set to “onboard control”, else the jumper setting defines the source of the switching signal.

Expand source code
def q_sw_src(self):  # queries the switching source
    """Queries the source of the switching signal.

    Queries the source of the switching signal. Output is SWSRC_TMR for onboard switching
    (from internal clock of the board), SWSRC_EXT for external switching via pin 6 (Ext.F) on the board main
    connector, or SWSRC_BTTN for the push button. Example: s_sw_src(SWSRC_EXT) to use the external signal to
    switch the source on/off. Note that this setting is useful only if the jumper on header H2 is set to
    “onboard control”, else the jumper setting defines the source of the switching signal."""
    if EMUL:
        z = 1
    else:
        self.ser.write(b'QSwSrc\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_sw_src -> " + str(z)
        print(y)
    self.swsrc = z
    return z
def q_type(self)

queries the type (master (multi-channel), slave (single channel))

Expand source code
def q_type(self):  # queries the type (master (multi-channel), slave (single channel))
    """queries the type (master (multi-channel), slave (single channel))"""
    if EMUL:
        z = "slave"
    else:
        self.ser.write(b'QVer\r')
        s = self._readline()  # returns a string in the form of "slave X" need to get slave
        s = s.decode("utf-8")
        z1 = s.split(" ")
        z = z1[0]
    if DEBUG:
        y = "q_ver -> " + str(z)
        print(y)
    return z
def q_v_mode(self)

Queries the voltage control mode:

Queries the voltage control mode:

VMODE_R internal voltage regulator, VMODE_EXT external voltage control. VMODE_O internal open loop control (on-board regulator disconnected).

Expand source code
def q_v_mode(self):  # queries the switching source
    """Queries the voltage control mode:

    Queries the voltage control mode:\n
    VMODE_R internal voltage regulator, VMODE_EXT external voltage control. VMODE_O internal open loop control
    (on-board regulator disconnected)."""
    if EMUL:
        z = 1
    else:
        self.ser.write(b'QVMode\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_v_mode -> " + str(z)
        print(y)
    self.vmode = z
    return z
def q_ver(self)

returns the current version of the firmware running on the board.

Expand source code
def q_ver(self):  # queries the firmware version
    """returns the current version of the firmware running on the board."""
    if EMUL:
        z = 7
    else:
        self.ser.write(b'QVer\r')
        s = self._readline()  # returns a string in the form of "slave X" need to get the value of X
        s = s.decode("utf-8")
        z1 = s.split(" ")
        z = int(z1[1])
    if DEBUG:
        y = "q_ver -> " + str(z)
        print(y)
    return z
def q_vmax(self)

Queries the maximal voltage of the board. The returned value is in volts.

Expand source code
def q_vmax(self):  # queries the voltage rating of the board
    """Queries the maximal voltage of the board. The returned value is in volts."""
    if EMUL:
        z = 5000
    else:
        self.ser.write(b'QVmax\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_vmax -> " + str(z)
        print(y)
    self.vmax = z
    return z
def q_vnow(self)

Queries the current feedback voltage of the HVPS.

The returned value is in volts. Note that it is not necessarily the voltage at the output of the HVPS, which also depends on the switching mode (whether the HVPS is off, in DC mode, or switching).

Expand source code
def q_vnow(self):  # queries the voltage output
    """Queries the current feedback voltage of the HVPS.

    The returned value is in volts.
    Note that it is not necessarily the voltage at the output of the HVPS, which also depends on the
    switching mode (whether the HVPS is off, in DC mode, or switching)."""
    if EMUL:
        z = 0
    else:
        self.ser.write(b'QVnow\r')
        try:
            z = int(self._readline())
        except ValueError:
            z = 0
    if DEBUG:
        y = "q_vnow -> " + str(z)
        print(y)
    self.vnow = z
    return z
def q_vset(self)

Queries the voltage set point. The returned value is in volts.

Expand source code
def q_vset(self):  # queries the voltage setpoint
    """Queries the voltage set point. The returned value is in volts."""
    if EMUL:
        z = 0
    else:
        self.ser.write(b'QVset\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_vset -> " + str(z)
        print(y)
    self.vset = z
    return z
def q_waveform_meas_pts(self, x)

queries the waveform measured point number x

queries the waveform measured point number x. Same as q_waveform_set_pts(), but instead of returning the set point value, it returns the voltage value read by the SHVPS internally. In order for QR to return meaningful values, the SHVPS must have been in Waveform mode (SWMODE_WFRM) for at least one cycle.

Queries the waveform point number x (0<=x<=255). returns a value between 0 and 255 representing 0 to 100% of current voltage set point. This is a low-level function provided to match the QR command of the HVPS communication protocol. It is easier to use download_waveform_meas_pts() to download the whole waveform from the HVPS

Expand source code
def q_waveform_meas_pts(self, x):  # queries the the measured voltage of the xth point of the
    # waveform (x starts at 0)
    """queries the waveform measured point number x

    queries the waveform measured point number x. Same as q_waveform_set_pts(), but instead of returning the set
    point value, it returns the voltage value read by the SHVPS internally. In order for QR to return meaningful
    values, the SHVPS must have been in Waveform mode (SWMODE_WFRM) for at least one cycle.\n
    Queries the waveform point number x (0<=x<=255). returns a value between 0 and 255 representing 0 to 100%
    of current voltage set point. This is a low-level function provided to match the QR command of the HVPS
    communication protocol. It is easier to use download_waveform_meas_pts() to download the whole waveform from
    the HVPS"""
    if EMUL:
        z = x
    else:
        string = 'QR '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "q_waveform_meas_point(" + str(x) + ") -> " + str(z)
        print(y)
    return z
def q_waveform_num_pts(self)

Queries how many data point are currently stored in the waveform

Expand source code
def q_waveform_num_pts(self):  # queries the number of points saved for the waveform
    """Queries how many data point are currently stored in the waveform"""
    if EMUL:
        z = 0   # returns 0 points
    else:
        self.ser.write(b'QPtot\r')
        z = int(self._readline())
    if DEBUG:
        y = "q_waveform_num_pts -> " + str(z)
        print(y)
    return z
def q_waveform_set_pts(self, x)

queries the waveform set point number x

queries the waveform set point number x (0<=x<=255) returns a value between 0 and 255 representing 0 to 100% of current voltage set point. This is a low-level function provided to match the QP command of the HVPS communication protocol. It is easier to use download_waveform_set_pts() to download the whole waveform from the HVPS

Expand source code
def q_waveform_set_pts(self, x):  # queries the the xth point of the waveform (x starts at 0)
    """queries the waveform set point number x

    queries the waveform set point number x (0<=x<=255) returns a value between 0 and 255 representing 0 to 100%
    of current voltage set point. This is a low-level function provided to match the QP command of the HVPS
    communication protocol. It is easier to use download_waveform_set_pts() to download the whole waveform from
    the HVPS"""
    if EMUL:
        z = x
    else:
        string = 'QP '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "q_waveform_set_point(" + str(x) + ") -> " + str(z)
        print(y)
    return z
def s_c0(self, x)

sets the calibration constant c0

This command automatically saves the parameter in the HVPS EEPROM

Expand source code
def s_c0(self, x):  # sets the calibration constant c0
    """sets the calibration constant c0

    <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
    if EMUL:
        z = x
    else:
        string = 'SC0 '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = float(self._readline())
    if DEBUG:
        y = "s_c0(" + str(x) + ") -> " + str(z)
        print(y)
    return z
def s_c1(self, x)

sets the calibration constant c1

This command automatically saves the parameter in the HVPS EEPROM

Expand source code
def s_c1(self, x):  # sets the calibration constant c1
    """sets the calibration constant c1

    <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
    if EMUL:
        z = x
    else:
        string = 'SC1 '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = float(self._readline())
    if DEBUG:
        y = "s_c1(" + str(x) + ") -> " + str(z)
        print(y)
    return z
def s_c2(self, x)

sets the calibration constant c2

This command automatically saves the parameter in the HVPS EEPROM

Expand source code
def s_c2(self, x):  # sets the calibration constant c2
    """sets the calibration constant c2

    <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
    if EMUL:
        z = x
    else:
        string = 'SC2 '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = float(self._readline())
    if DEBUG:
        y = "s_c2(" + str(x) + ") -> " + str(z)
        print(y)
    return z
def s_cycle(self, x)

Sets the number of switching cycles

Sets the number of switching cycles to perform when the HVPS is in switching mode. The maximum value is 65535. A value of 0 means continuous switching (unlimited number of cycles). For any value other than 0, the HVPS will change to switching mode 0 (HVPS off, SWMODE_OFF, after the desired number of cycles is reached. A new series of switching cycles can be can be initiated by placing the HVPS back in switching mode (SWMODE_SW). When you call s_cycle(), the cycle counter is reset to 0. Example: s_cycle(1000) to switch 1000 times at the selected frequency and then stop. Note that s_cycles configures the HVPS to switch for a limited number of time. If the HVPS is in switching mode (SWMODE_SW), then the cycles will start at once. If the HVPS is in off mode (SWMODE_OFF) or in DC mode (SWMODE_DC), the cycles will start once the HVPS is put in Switching mode with s_sw_mode(SWMODE_SW).

The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory

input: x: number of cycles (0 to 65535)

output: number of cycles accepted by HVPS

Expand source code
def s_cycle(self, x):  # sets the number of cycles
    """Sets the number of switching cycles

    Sets the number of switching cycles to perform when the HVPS is in switching mode.
    The maximum value is 65535. A value of 0 means continuous switching (unlimited number of cycles).
    For any value other than 0, the HVPS will change to switching mode 0 (HVPS off, SWMODE_OFF, after the desired
    number of cycles is reached. A new series of switching cycles can be can be initiated by placing the HVPS
    back in switching mode (SWMODE_SW). When you call s_cycle(), the cycle counter is reset to 0.
    Example: s_cycle(1000) to switch 1000 times at the selected frequency and then stop.
    Note that s_cycles configures the HVPS to switch for a limited number of time. If the HVPS is in switching
    mode (SWMODE_SW), then the cycles will start at once. If the HVPS is in off mode (SWMODE_OFF) or in
    DC mode (SWMODE_DC), the cycles will start once the HVPS is put in Switching mode with s_sw_mode(SWMODE_SW).\n
    The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
    save() command enables to save this parameter in memory\n
    input: x: number of cycles (0 to 65535)\n
    output: number of cycles accepted by HVPS"""
    if EMUL:
        z = x
    else:
        x = constrain(x, 0, 65535)
        string = 'SCycle '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_cycle(" + str(x) + ") -> " + str(z)
        print(y)
    self.cycles = z
    return z
def s_f(self, x)

Sets the frequency of the signal

Sets the frequency of the signal when the HVPS is in switching mode (SWMODE_SW).

The value returned is the new frequency, taking quantification into account.

The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory

input: x: frequency in Hz between 0.001 and 1000

output: frequency accepted by HVPS

Expand source code
def s_f(self, x):  # sets the frequency
    """Sets the frequency of the signal

    Sets the frequency of the signal when the HVPS is in switching mode (SWMODE_SW).\n
    The value returned is the new frequency, taking quantification into account.\n
    The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
    save() command enables to save this parameter in memory\n
    input: x: frequency in Hz between 0.001 and 1000\n
    output: frequency accepted by HVPS"""
    if EMUL:
        z = 1
    else:
        x = constrain(x, 0.001, 1000.0)
        string = 'SF '
        string = string + f"{x:.3f}" + '\r'
        string = string.encode()
        self.ser.write(string)
        z = float(self._readline())
    if DEBUG:
        y = "s_f(" + str(x) + ") -> " + str(z)
        print(y)
    self.f = z
    return z
def s_i2c(self, x)

sets the I2C address of the board. Only useful if board to be used in multichannel configuration

This command automatically saves the parameter in the HVPS EEPROM

input: x: I2C address to assign

Expand source code
def s_i2c(self, x):  # sets the I2C address
    """sets the I2C address of the board. Only useful if board to be used in multichannel configuration

    <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n
    input: x: I2C address to assign"""
    if EMUL:
        z = 12  # random returned i2c value in case we emulate the presence of a board
    else:
        x = constrain(x, 0, 127)
        string = 'SI2C '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_I2C(" + str(x) + ") -> " + str(z)
        print(y)
    self.i2c = z
    return z
def s_kd(self, x)

sets parameters Kd of the PID regulator.

This command automatically saves the parameter in the HVPS EEPROM

Expand source code
def s_kd(self, x):  # sets the PID gain Kd
    """sets parameters Kd of the PID regulator.

    <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
    if EMUL:
        z = x
    else:
        string = 'SKd '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = float(self._readline())
    if DEBUG:
        y = "s_kd(" + str(x) + ") -> " + str(z)
        print(y)
    return z
def s_ki(self, x)

""sets parameters Ki of the PID regulator.

This command automatically saves the parameter in the HVPS EEPROM

Expand source code
def s_ki(self, x):  # sets the PID gain Ki
    """""sets parameters Ki of the PID regulator.

    <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
    if EMUL:
        z = x
    else:
        string = 'SKi '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = float(self._readline())
    if DEBUG:
        y = "s_ki(" + str(x) + ") -> " + str(z)
        print(y)
    return z
def s_kp(self, x)

sets parameters Kp of the PID regulator.

This command automatically saves the parameter in the HVPS EEPROM

Expand source code
def s_kp(self, x):  # sets the PID gain Kp
    """sets parameters Kp of the PID regulator.

    <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n"""
    if EMUL:
        z = x
    else:
        string = 'SKp '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = float(self._readline())
    if DEBUG:
        y = "s_kp(" + str(x) + ") -> " + str(z)
        print(y)
    return z
def s_latch_mode(self, x)

Defines the behaviour of the push button

Defines the behaviour of the push button, when the switching source of the HVPS is set to the push button (SWSRC_BTTN, c.f. s_sw_src() command above). Accepted values are 0 and 1: 0 for a push button behaviour (i.e. the high voltage is turned on as long as the button is pressed), and 1 for a latching switch behaviour (i.e. press once to turn the high voltage on, and press a second time to turn it off).

The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory

input: x: 0 or 1

output: latching mode as understood by the HVPS

Expand source code
def s_latch_mode(self, x):  # sets the latch mode of the push button
    """Defines the behaviour of the push button

    Defines the behaviour of the push button, when the switching source of the HVPS is set to the push button
    (SWSRC_BTTN, c.f. s_sw_src() command above). Accepted values are 0 and 1: 0 for a push button behaviour
    (i.e. the high voltage is turned on as long as the button is pressed),
    and 1 for a latching switch behaviour (i.e. press once to turn the high voltage on, and press a second time
    to turn it off).\n
    The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
    save() command enables to save this parameter in memory\n
    input: x: 0 or 1\n
    output: latching mode as understood by the HVPS"""
    if EMUL:
        z = x
    else:
        if x > 1:
            x = 1
        string = 'SLatchMode '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_latch_mode(" + str(x) + ") -> " + str(z)
        print(y)
    self.latch = z
    return z
def s_name(self, x)

Sets the name of the HVPS.

Sets the name of the HVPS. 20 characters maximum

This command automatically saves the parameter in the HVPS EEPROM

input: x=Name of the HVPS output: name accepted by HVPS

Expand source code
def s_name(self, x):  # set the name of the HVPS
    """Sets the name of the HVPS.

    Sets the name of the HVPS. 20 characters maximum\n
    <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n
    input: x=Name of the HVPS
    output: name accepted by HVPS"""
    if EMUL:
        z = x
    elif len(x) < 21:
        string = 'SName '
        string = string + x + '\r'
        string = string.encode()
        self.ser.write(string)
        z = self._readline()
    else:
        z = 'too long'
    if DEBUG:
        y = "s_kd(" + str(x) + ") -> " + str(z)
        print(y)
    return z
def s_power_jack(self, x)

Defines if power if provided by the power jack.

Defines if power if provided by the power jack, s_power_jack(1) (default), or through 5V pin, s_power_jack(0). As the default is power jack 1, You only need the use this command with SPowJack 0 if you wish to use the HVPS in the standalone configuration with touchscreen and battery, or in the multichannel configuration.

This command automatically saves the parameter in the HVPS EEPROM

input: x: 0 or 1 output: power jack mode accepted by HVPS

Expand source code
def s_power_jack(self, x):  # sets the configuration of the HVPS (whether power comes from power jack)
    """Defines if power if provided by the power jack.

    Defines if power if provided by the power jack, s_power_jack(1) (default), or through 5V pin, s_power_jack(0).
    As the default is power jack 1, You only need the use this command with SPowJack 0 if you wish to use the HVPS
    in the standalone configuration with touchscreen and battery, or in the multichannel configuration.\n
    <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n
    input: x: 0 or 1
    output: power jack mode accepted by HVPS"""
    if EMUL:
        z = x
    else:
        if x > 1:
            x = 1
        string = 'SPowJack '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_power_jack(" + str(x) + ") -> " + str(z)
        print(y)
    self.config = not z     # updates the configuration status of the HVPS
    return z
def s_pwm(self, x)

Set the voltage output as a PWM value

Defines the functioning set point of the HV programmable source as a 10-bit (0-1023) raw PWM value. Although this command can be used at any time, it mainly useful when the HVPS voltage control mode is internal openloop, VMODE_O (see set_vmode() command).

input: x: PWM set point (0-1023)

output: PWM set point accepted by HVPS

Expand source code
def s_pwm(self, x):  # sets the pwm value
    """ Set the voltage output as a PWM value

    Defines the functioning set point of the HV programmable source as a 10-bit (0-1023) raw PWM value.
    Although this command can be used at any time, it mainly useful when the HVPS voltage control mode is
    internal openloop, VMODE_O (see set_vmode() command).\n
    input: x: PWM set point (0-1023)\n
    output: PWM set point accepted by HVPS"""
    if EMUL:
        z = 0
    else:
        x = constrain(x, 0, 1023)
        string = 'SPWM '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_pwm -> " + str(z)
        print(y)
    return z
def s_st_dur(self, x)

Sets the duration of the strobe/trigger pulse as a fraction of the period of the HV output.

Sets the duration of the strobe/trigger pulse as a fraction of the period of the HV output. Usage: s_st_dur(xxx), with xxx being the fraction of the period of the HV output during which the strobe/trigger pulse must remain high (0: constantly off, 127: on during 50% of the period, and 255: constantly on).

The duration of the pulse is used in strobe mode ST_MODE_FIXED (fixed position) and STMODE_SWEEP (sweep mode)

input: x: duration of the strobe pulse (0-255, representing 0 to 100% duty cycle)

output: pulse duration accepted by the HVPS

Expand source code
def s_st_dur(self, x):  # sets the duration of the strobe pulse
    """Sets the duration of the strobe/trigger pulse as a fraction of the period of the HV output.

    Sets the duration of the strobe/trigger pulse as a fraction of the period of the HV output.
    Usage: s_st_dur(xxx), with xxx being the fraction of the period of the HV output during which the
    strobe/trigger pulse must remain high
    (0: constantly off, 127: on during 50% of the period, and 255: constantly on).\n
    The duration of the pulse is used in strobe mode ST_MODE_FIXED (fixed position) and STMODE_SWEEP (sweep mode)\n
    input: x: duration of the strobe pulse (0-255, representing 0 to 100% duty cycle)\n
    output: pulse duration accepted by the HVPS"""
    if EMUL:
        z = x
    else:
        x = constrain(x, 0, 255)
        string = 'SStDur '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_st_dur(" + str(x) + ") -> " + str(z)
        print(y)
    self.stdur = z
    return z
def s_st_mode(self, x)

Sets the strobe mode.

Sets the strobe mode. Usage: s_st_mode(X), with X being

STMODE_OFF: trigger/strobe pulse off: no signal is generated.

STMODE_FIXED: fixed position: a trigger/strobe signal is generated at a fixed position, with a fixed duration. By default, the pulse is synchronized with the HV switching signal, but the position and duration of the pulse can be defined by the user (see next s_st_pos()).

STMODE_SWEEP: sweep mode: the position of the trigger/strobe pulse is shifted along the HV signal at a user-defined speed. The HVPS must be in switching mode (SWMODE_SW) for the trigger pulse to be generated. The trigger pulse is generated on pin T of the multi-purpose 10-pins header on the HVPS board.

input: x: STMODE_OFF or STMODE_FIXED, or STMODE_SWEEP

output: strobe mode as understood by HVPS

Expand source code
def s_st_mode(self, x):  # sets the strobe mode
    """Sets the strobe mode.

    Sets the strobe mode. Usage: s_st_mode(X), with X being\n
    STMODE_OFF: trigger/strobe pulse off: no signal is generated.\n
    STMODE_FIXED: fixed position: a trigger/strobe signal is generated at a fixed position, with a fixed duration.
    By default, the pulse is synchronized with the HV switching signal, but the position and duration of the pulse
    can be defined by the user (see next s_st_pos()).\n
    STMODE_SWEEP: sweep mode: the position of the trigger/strobe pulse is shifted along the HV signal at a
    user-defined speed. The HVPS must be in switching mode (SWMODE_SW) for the trigger pulse to be generated.
    The trigger pulse is generated on pin T of the multi-purpose 10-pins header on the HVPS board.\n
    input: x: STMODE_OFF or STMODE_FIXED, or STMODE_SWEEP\n
    output: strobe mode as understood by HVPS"""
    if EMUL:
        z = x
    else:
        if x > STMODE_SWEEP:
            x = STMODE_OFF
        string = 'SStMode '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_st_mode(" + str(x) + ") -> " + str(z)
        print(y)
    self.stmode = z
    return z
def s_st_pos(self, x)

Sets the position of the rising edge of the strobe pulse with respect to the HV output.

Sets the position of the rising edge of the strobe pulse with respect to the HV output. Usage: s_st_pos(xxx), with xxx a value between 0 and 255, representing the position of the rising edge of the trigger pulse as a fraction of the period T of the HV signal. A value of 0 means that the rising edge of the trigger pulse is coincident with the rising edge of the HV output.

A value of 127 means that the rising edge of the trigger pulse is coincident with the falling edge of the HV output. The position of the pulse is only used when the strobe pulse mode is STMODE_FIXED (fixed position).

input: x: position of the trigger pulse (0 to 255)

output: position of the trigger pulse accepted by the HVPS

Expand source code
def s_st_pos(self, x):  # sets the position of the strobe pulse
    """Sets the position of the rising edge of the strobe pulse with respect to the HV output.

    Sets the position of the rising edge of the strobe pulse with respect to the HV output.
    Usage: s_st_pos(xxx), with xxx a value between 0 and 255, representing the position of the rising edge of the
    trigger pulse as a fraction of the period T of the HV signal. A value of 0 means that the rising edge of the
    trigger pulse is coincident with the rising edge of the HV output.\n
    A value of 127 means that the rising edge of the trigger pulse is coincident with the falling edge of the HV
    output. The position of the pulse is only used when the strobe pulse mode is STMODE_FIXED (fixed position).\n
    input: x: position of the trigger pulse (0 to 255)\n
    output: position of the trigger pulse accepted by the HVPS"""
    if EMUL:
        z = x
    else:
        x = constrain(x, 0, 255)
        string = 'SStPos '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_st_pos(" + str(x) + ") -> " + str(z)
        print(y)
    self.stpos = z
    return z
def s_st_sweep(self, x)

sets the sweep time in ms.

sets the sweep time in ms. Usage s_st_sweep(xxx), where xxx is the time (in ms) that it takes to sweep the strobe/trigger pulse over a complete period of the HV output. This parameter is only used in strobe mode STMODE_SWEEP (sweep mode). If the pulse is used to drive a strobe LED, the sweep time defines the apparent period of the motion. Logically, the period set with s_st_sweep much be considerably longer than the period of the HV signal.

input: x: period of the sweep duration in ms

output: sweep duration accepted by HVPS

Expand source code
def s_st_sweep(self, x):  # sets the sweep period (im ms) when in sweep mode
    """sets the sweep time in ms.

    sets the sweep time in ms. Usage s_st_sweep(xxx), where xxx is the time  (in ms) that it takes to sweep the
    strobe/trigger pulse over a complete period of the HV output. This parameter is only used in strobe mode
    STMODE_SWEEP (sweep mode). If the pulse is used to drive a strobe LED, the sweep time defines the apparent
    period of the motion. Logically, the period set with s_st_sweep much be considerably longer than the period
    of the HV signal.\n
    input: x: period of the sweep duration in ms\n
    output: sweep duration accepted by HVPS"""
    if EMUL:
        z = x
    else:
        x = constrain(x, 0, 65535)
        string = 'SStSw '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_st_sweep(" + str(x) + ") -> " + str(z)
        print(y)
    self.stsweep = z
    return z
def s_sw_mode(self, x)

Sets the switching mode of the HVPS.

Sets the switching mode of the HVPS. Four possible values: SWMODE_OFF: HVPS is off (0 V output irrespective of voltage set point), SWMODE_DC: the HVPS is in DC mode with a constant output voltage at the desired set point, SWMODE_SW: the HVPS is switching at the desired frequency between 0V and Vset, and SWMODE_WFRM: the HVPS is in user-defined waveform mode. Setting the switching mode is only effective if the switching source is SWSRC_TMR (onboard timer switching).

The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory

input: x: SWMODE_OFF, SWMODE_DC, SWMODE_SW, or SWMODE_WFRM

output: Switching mode set by the HVPS

Expand source code
def s_sw_mode(self, x):  # sets the switching mode
    """Sets the switching mode of the HVPS.

    Sets the switching mode of the HVPS. Four possible values: SWMODE_OFF: HVPS is off (0 V output irrespective
    of voltage set point), SWMODE_DC: the HVPS is in DC mode with a constant output voltage at the desired
    set point, SWMODE_SW: the HVPS is switching at the desired frequency between 0V and Vset, and SWMODE_WFRM:
    the HVPS is in user-defined waveform mode. Setting the switching mode is only effective if the switching
    source is SWSRC_TMR (onboard timer switching).\n
    The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
    save() command enables to save this parameter in memory\n
    input: x: SWMODE_OFF, SWMODE_DC, SWMODE_SW, or SWMODE_WFRM\n
    output: Switching mode set by the HVPS"""
    if EMUL:
        z = x
    else:
        if x > SWMODE_WFRM:
            x = SWMODE_OFF
        string = 'SSwMode '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_sw_mode(" + str(x) + ") -> " + str(z)
        print(y)
    self.swmode = z
    return z
def s_sw_src(self, x)

Sets the source of the switching signal.

Sets the source of the switching signal. Accepted values are: SWSRC_TMR for onboard switching (from internal clock of the board), SWSRC_EXT for external switching via pin 6 (Ext.F) on the board main connector, or SWSRC_BTTN for the push button. Example: s_sw_src(SWSRC_EXT) to use the external signal to switch the source on/off. Note that this setting is useful only if the jumper on header H2 is set to “onboard control”, else the jumper setting defines the source of the switching signal.

The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory

input: x: SWSRC_TMR, or SWSRC_EXT, or SWSRC_BTTN

Expand source code
def s_sw_src(self, x):  # sets the switching source
    """Sets the source of the switching signal.

    Sets the source of the switching signal. Accepted values are: SWSRC_TMR for onboard switching
    (from internal clock of the board), SWSRC_EXT for external switching via pin 6 (Ext.F) on the board main
    connector, or SWSRC_BTTN for the push button. Example: s_sw_src(SWSRC_EXT) to use the external signal to
    switch the source on/off. Note that this setting is useful only if the jumper on header H2 is set to
    “onboard control”, else the jumper setting defines the source of the switching signal.\n
    The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
    save() command enables to save this parameter in memory\n
    input: x: SWSRC_TMR, or SWSRC_EXT, or SWSRC_BTTN"""
    if EMUL:
        z = x
    else:
        if x > SWSRC_BTTN:
            x = SWSRC_TMR
        string = 'SSwSrc '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_sw_src(" + str(x) + ") -> " + str(z)
        print(y)
    self.swsrc = z
    return z
def s_v_mode(self, x)

Sets the voltage control mode

Sets the voltage control mode (i.e. how is the value of the output voltage controlled):

VMODE_R for internal voltage regulator (regulates the voltage to the value defined with the Vset command).

SMODE_EXT external voltage control (sets the output voltage according to the control voltage applied on pin 5 (Ext.V) of the main connector of the board. The input voltage range is 0 to 5V.

VMODE_O (that's an O like in open) internal open loop control (on-board regulator disconnected).

The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory

input: x: VMODE_R, VMODE_EXT, VMODE_O

Output: Voltage control mode accepted by the HVPS

Expand source code
def s_v_mode(self, x):  # sets the voltage control mode
    """Sets the voltage control mode

    Sets the voltage control mode (i.e. how is the value of the output voltage controlled):\n
    VMODE_R for internal voltage regulator (regulates the voltage to the value defined with the Vset command).\n
    SMODE_EXT external voltage control (sets the output voltage according to the control voltage applied on pin 5
    (Ext.V) of the main connector of the board. The input voltage range is 0 to 5V.\n
    VMODE_O (that's an O like in open) internal open loop control (on-board regulator disconnected).\n
    The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
    save() command enables to save this parameter in memory\n
    input: x: VMODE_R, VMODE_EXT, VMODE_O\n
    Output: Voltage control mode accepted by the HVPS"""
    if EMUL:
        z = x
    else:
        if x > VMODE_O:
            x = VMODE_R
        string = 'SVMode '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_v_mode(" + str(x) + ") -> " + str(z)
        print(y)
    self.vmode = z
    return z
def s_vmax(self, x)

sets the voltage rating of the SHVPS.

sets the voltage rating of the SHVPS. Must match the EMCO DC/DC converter rating.

This command automatically saves the parameter in the HVPS EEPROM

input: x:voltage rating of HVPS in Volt

Expand source code
def s_vmax(self, x):  # sets the maximum voltage rating of the board
    """sets the voltage rating of the SHVPS.

    sets the voltage rating of the SHVPS. Must match the EMCO DC/DC converter rating.\n
    <b>This command automatically saves the parameter in the HVPS EEPROM</b>\n
    input: x:voltage rating of HVPS in Volt"""
    if EMUL:
        z = x
    else:
        x = constrain(x, 0, 10000)
        string = 'SVmax '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_vmax(" + str(x) + ") -> " + str(z)
        print(y)
    self.vmax = z
    return z
def s_vset(self, x)

Sets the output voltage of the HVPS

input: x: voltage set point (int)

output: voltage set point accepted by HVPS

The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory

Although this command can be used at any time, it is mainly useful when the HVPS voltage control mode is internal regulator (VMODE_R); see s_vmode() command.

Expand source code
def s_vset(self, x):  # sets the output voltage
    """ Sets the output voltage of the HVPS

    input: x: voltage set point (int)\n
    output: voltage set point accepted by HVPS\n
    The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
    save() command enables to save this parameter in memory\n
    Although this command can be used at any time, it is mainly useful when the HVPS voltage control mode is
    internal regulator (VMODE_R); see s_vmode() command."""
    if EMUL:
        z = x
    else:
        x = constrain(x, 0, self.vmax)
        string = 'SVset '
        string = string + str(x) + '\r'
        string = string.encode()
        self.ser.write(string)
        z = int(self._readline())
    if DEBUG:
        y = "s_vset(" + str(x) + ") -> " + str(z)
        print(y)
    self.vset = z
    return z
def s_waveform_point(self, x)

Add a point to the user waveform.

Add a point to the user waveform. Usage: s_waveform_point(xxx), with xxx between 0 and 255 representing 0 to 100% of the voltage setpoint.

A new waveform is defined by issuing clear_waveform() (to clear the previous waveform), followed by a series of s_waveform_point(xxx) to define the points of the new waveform. The maximal number of allowed points is 255. This is a low-level function provided to match the 'SP' command of the HVPS communication protocol. However, It is easier to use upload_waveform() to upload a complete waveform to the HVPS in one go, or to use upload_std_waveform() to upload some customisable standard waveforms.

The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory

input: x: set point to add to the current waveform. 0-255 representing 0-100% of voltage set point.

output: accepted set point (or -1 if waveform full)

Expand source code
def s_waveform_point(self, x):  # Add a point to the waveform
    """Add a point to the user waveform.

    Add a point to the user waveform. Usage: s_waveform_point(xxx), with xxx between 0 and 255 representing
    0 to 100% of the voltage setpoint.\n
    A new waveform is defined by issuing clear_waveform() (to clear the previous waveform), followed by a series of
    s_waveform_point(xxx) to define the points of the new waveform. The maximal number of allowed points is 255.
    This is a low-level function provided to match the 'SP' command of the HVPS communication protocol. However,
    It is easier to use upload_waveform() to upload a complete waveform to the HVPS in one go, or to use
    upload_std_waveform() to upload some customisable standard waveforms.\n
    The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
    save() command enables to save this parameter in memory\n
    input: x: set point to add to the current waveform. 0-255 representing 0-100% of voltage set point.\n
    output: accepted set point (or -1 if waveform full)"""
    s = [-1, -1]
    x = constrain(x, 0, 255)
    if EMUL:
        z = x
    else:
        num_pts = self.q_waveform_num_pts()   # queries how many points currently saved in memory
        if num_pts < 255:   # can only add a point if less than 255 points currently saved
            string = 'SP '
            string = string + str(x) + '\r'
            string = string.encode()
            self.ser.write(string)
            z = self._readline()
            z = z.decode("utf-8")
            s = z.split(",")
            z = s[1]
        else:
            z = -1
    if DEBUG:
        y = "s_waveform_point(" + str(x) + ") -> " + str(s[0]) + "," + str(s[1])
        print(y)
    return z
def save(self)

save current HVPS parameters into the memory

This command saves the following parameters in EEPROM: Voltage setpoint, Frequency, Frequency divider, Source of switching signal (external, internal, button), Switching mode (off, DC output, switching output), Voltage mode (internal voltage regulator or following external signal), and number of switching cycles. It also saves the user-defined waveform into the EEPROM. This is useful so that it is not necessary to reconfigure the HVPS for the desired behavior every time it is powered up, for example when the HVPS is meant to be used without computer (when connected to a demo device). Note that when the HVPS is used with a computer, it is safer to save a voltage setpoint of 0V (and/or a switching mode 0 (box off)) so as to be sure that no high voltage is present at the output when the board is powered up (and before the computer has the time to initialise it to 0V output).

Expand source code
def save(self):  # save current HVPS parameters into the memory
    """save current HVPS parameters into the memory

    This command saves the following parameters in EEPROM: Voltage setpoint, Frequency, Frequency divider,
    Source of switching signal (external, internal, button), Switching mode (off, DC output, switching output),
    Voltage mode (internal voltage regulator or following external signal), and number of switching cycles.
    It also saves the user-defined waveform into the EEPROM. This is useful so that it is not necessary to
    reconfigure the HVPS for the desired behavior every time it is powered up, for example when the HVPS is
    meant to be used without computer (when connected to a demo device). Note that when the HVPS is used with a
    computer, it is safer to save a voltage setpoint of 0V (and/or a switching mode 0 (box off)) so as to be sure
    that no high voltage is present at the output when the board is powered up (and before the computer has the
    time to initialise it to 0V output)."""
    if EMUL:
        z = 1
    else:
        self.ser.write(b'Save\r')
        z = int(self._readline())
    if DEBUG:
        y = "save -> " + str(z)
        print(y)
    return z
def transmit(self, x)

Sends a direct command to the HVPS

Sends a command to the HVPS. List of commands: https://petapicovoltron.com/software/direct-communication-with-the-hvps/ This function is used by the main() functions in case this file is run as a script and provides an interactive terminal session with the HVPS. However, when using the library, the specific functions provided by the library should be used instead

input: x: a string to send to the HVPS

output: The reply from the HVPS (string)

Expand source code
def transmit(self, x):  # transmits a command directly to the HVPS
    """Sends a direct command to the HVPS

    Sends a command to the HVPS. List of commands:
    https://petapicovoltron.com/software/direct-communication-with-the-hvps/
    This function is used by the main() functions in case this file is run as a script
    and provides an interactive terminal session with the HVPS. However, when using
    the library, the specific functions provided by the library should be used instead\n
    input: x: a string to send to the HVPS\n
    output: The reply from the HVPS (string)"""
    if EMUL:
        z = b'null\r\n'
    else:
        self.ser.write(x)
        z = self._readline2()
    if DEBUG:
        print(x)
        print(z)
    return z
def upload_std_waveform(self, func=0, sr=False, n=100, b=0.15)

Upload a customisable standard waveform to the HVPS

inputs:

func: FUNC_SINE (a sine wave with an offset to be between 0 and Voltage set point), FUNC_TRI (a triangle function), FUNC_TRAP (a Trapezoid function), FUNC_CSTM (a custom waveform. Points (a maximum number of 255 points) should be defined in a file named waveform.txt located alongside this library. There should be 1 point per line, each point between 0 and 1, representing 0 to 100% of the voltage set point)

sr: (square root) True or False. In case the HVPS is used to drive dielectric elastomer actuators, there is a quadratic relationship between voltage and actuation strain. True: a square root correction is applied so that the actuation strain will roughly have the chosen profile. False: No correction applied/n n: number of point in the waveform. Max is 255. It depends on the frequency at which the waveform will be produced, For a 1Hz signal, 100 points are adequate. Reduce the number of points for higher frequencies b: This applies only for the FUNC_TRAP function and defines the percentage of the period that the raising (and falling) edge should take. The value should be smaller than 0.5 (at which point the waveform becomes a triangle).

The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory

Expand source code
def upload_std_waveform(self, func=FUNC_SINE, sr=False, n=100,  b=0.15):
    """Upload a customisable standard waveform to the HVPS

    inputs:\n
    func: FUNC_SINE (a sine wave with an offset to be between 0 and Voltage set point), FUNC_TRI
    (a triangle function), FUNC_TRAP (a Trapezoid function), FUNC_CSTM (a custom waveform. Points (a maximum number
    of 255 points) should be defined in a file named waveform.txt located alongside this library. There should be
    1 point per line, each point between 0 and 1, representing 0 to 100% of the voltage set point)\n
    sr: (square root) True or False. In case the HVPS is used to drive dielectric elastomer actuators, there is
    a quadratic relationship between voltage and actuation strain. True: a square root correction is applied so
    that the actuation strain will roughly have the chosen profile. False: No correction applied/n
    n: number of point in the waveform. Max is 255. It depends on the frequency at which the waveform will be
    produced, For a 1Hz signal, 100 points are adequate. Reduce the number of points for higher frequencies
    b: This applies only for the FUNC_TRAP function and defines the percentage of the period that the raising
    (and falling) edge should take. The value should be smaller than 0.5 (at which point the waveform becomes a
    triangle).\n
     The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
    save() command enables to save this parameter in memory\n"""
    pts = []
    n = constrain(n, 1, 255)     # n must be between 1 and 255 points
    b = constrain(b, 0, 0.5)      # b must be between 0 and 0.5
    if sr:      # if we want the square root of the signal (because of the quadratic relationship between voltage
        #  and strain for DEAs)
        power = 0.5
    else:
        power = 1.0

    if func == FUNC_CSTM:   # custom user waveform
        try:
            fp = open('./waveform.txt', 'r')
        except FileNotFoundError:
            if DEBUG:
                print("Custom waveform must be in ./waveform.txt, but file is not found")
            fp = 0
        if fp:
            list_of_points = fp.readlines()
            for point in list_of_points:
                try:
                    point = int(255*(float(point) ** power))
                except ValueError:
                    point = 0
                    if DEBUG:
                        print("Error when reading point for custom waveform. Each line in the file ./waveform.txt "
                              "must contain a single floating point number")
                point = constrain(point, 0, 255)    # points should be between 0 and 255
                pts.append(point)
            fp.close()
    else:   # if other standard functions are chosen
        for i in range(n):
            p = 0       # p is between 0 to 1 representing percentage of voltage set point
            if func == FUNC_SINE:   # Sine + offset waveform
                p = (0.5+0.5*sin(2*pi/n*i)) ** power
            elif func == FUNC_TRAP:  # trapeze waveform
                if i <= b*n:    # the ramp up of the trapeze
                    p = (i/b/n) ** power
                elif i <= n/2:  # holding time
                    p = 1
                elif i <= (n/2 + b*n):    # ramp down
                    p = (1-(i - n/2) / b / n) ** power
                else:
                    p = 0
            elif func == FUNC_TRI:
                if i <= n/2:    # Raising edge
                    p = (2/n*i) ** power
                else:
                    p = (1 - 2 / n * (i - n / 2)) ** power
            p = int(p*255)
            pts.append(p)

    self.upload_waveform(pts)       # uploads the waveform to the HVPS
def upload_waveform(self, x)

upload a user-defined waveform to the HVPS

upload a user-defined waveform to the HVPS. It starts by clearing the current waveform and then it upload an new list of points. It also updates the member variable self.waveform_pts with the new list of points.

The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory

input: x: [p1, p2, p3, …, pn] where pn is the nth point of the waveform, between 0 and 255, representing 0 to 100% of the current voltage set point. n is limited to 255.

output: none

Expand source code
def upload_waveform(self, x):   # upload a list (x) of waveform set points to the HVPS
    """upload a user-defined waveform to the HVPS

    upload a user-defined waveform to the HVPS. It starts by clearing the current waveform and then it upload
    an new list of points. It also updates the member variable self.waveform_pts with the new list of points.\n
    The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the
    save() command enables to save this parameter in memory\n
    input: x: [p1, p2, p3, ..., pn] where pn is the nth point of the waveform, between 0 and 255, representing 0 to
    100% of the current voltage set point. n is limited to 255.\n
    output: none"""
    self.clear_waveform()   # starts by clearing the waveform
    self.waveform_pts.clear()   # empty the current list of point of the local copy
    if len(x) > 255:    # waveform limited to 255 points
        x = x[:255]
    for point in x:
        point = constrain(point, 0, 255)
        self.waveform_pts.append(self.s_waveform_point(point))      # upload the point and add it to the local copy
class HVPSMem
Expand source code
class HVPSMem:  # structure to store the memory information of the HVPS
    def __init__(self):
        self.vset = 0
        self.f = 0
        self.swsrc = 0
        self.swmode = 0
        self.vmode = 0
        self.kp = 0
        self.ki = 0
        self.kd = 0
        self.c0 = 0
        self.c1 = 0
        self.c2 = 0
        self.cycles = 0
        self.latch = 0
        self.vmax = 0  # Vmax, ver and i2c address are not returned by the q_mem function, because they have dedicated
        # query function. They are added to the structure so that all information regarding the configuration of the
        # board can be grouped in a single variable. (Name should be added)
        self.i2c = 0
        self.ver = 0