From a0f8a2a9038e10e83395d499b33fab133573aab4 Mon Sep 17 00:00:00 2001 From: "Matthias@Dell" Date: Sun, 18 Jun 2023 17:38:10 +0200 Subject: [PATCH] renamed to m-teng --- .gitignore | 1 + MANIFEST.in | 2 + bspc_rule.sh | 1 + k-teng/keithley/measure.py | 107 ----- k-teng/materials | 4 - k-teng/test.py | 49 -- k-teng/testcurve.py | 33 -- k-teng/utility/testing.py | 38 -- {k-teng => m_teng}/__init__.py | 0 .../keithley => m_teng/backends}/__init__.py | 0 .../backends/arduino}/__init__.py | 0 m_teng/backends/arduino/arduino.py | 123 +++++ m_teng/backends/arduino/measure.py | 59 +++ m_teng/backends/keithley/__init__.py | 0 .../backends}/keithley/keithley.py | 50 ++- m_teng/backends/keithley/measure.py | 93 ++++ .../keithley_scripts}/buffer_reset.lua | 0 .../keithley_scripts}/smua_reset.lua | 0 .../m_teng_interactive.py | 219 +++++---- m_teng/update_funcs.py | 123 +++++ m_teng/utility/__init__.py | 0 {k-teng => m_teng}/utility/data.py | 0 {k-teng => m_teng}/utility/file_io.py | 0 m_teng/utility/testing.py | 17 + measurement.ipynb | 419 ------------------ pyproject.toml | 35 ++ readme.md | 36 +- 27 files changed, 622 insertions(+), 787 deletions(-) create mode 100644 MANIFEST.in create mode 100755 bspc_rule.sh delete mode 100644 k-teng/keithley/measure.py delete mode 100644 k-teng/materials delete mode 100644 k-teng/test.py delete mode 100644 k-teng/testcurve.py delete mode 100644 k-teng/utility/testing.py rename {k-teng => m_teng}/__init__.py (100%) rename {k-teng/keithley => m_teng/backends}/__init__.py (100%) rename {k-teng/utility => m_teng/backends/arduino}/__init__.py (100%) create mode 100644 m_teng/backends/arduino/arduino.py create mode 100644 m_teng/backends/arduino/measure.py create mode 100644 m_teng/backends/keithley/__init__.py rename {k-teng => m_teng/backends}/keithley/keithley.py (56%) create mode 100644 m_teng/backends/keithley/measure.py rename {scripts => m_teng/keithley_scripts}/buffer_reset.lua (100%) rename {scripts => m_teng/keithley_scripts}/smua_reset.lua (100%) rename k-teng/k_teng_interactive.py => m_teng/m_teng_interactive.py (67%) create mode 100644 m_teng/update_funcs.py create mode 100644 m_teng/utility/__init__.py rename {k-teng => m_teng}/utility/data.py (100%) rename {k-teng => m_teng}/utility/file_io.py (100%) create mode 100644 m_teng/utility/testing.py delete mode 100644 measurement.ipynb create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore index eeb514c..3450e0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__ .ipynb_checkpoints +testing diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..4b5a330 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include regina/package-data/* +include regina/sql/*.sql diff --git a/bspc_rule.sh b/bspc_rule.sh new file mode 100755 index 0000000..6ea53d0 --- /dev/null +++ b/bspc_rule.sh @@ -0,0 +1 @@ +bspc rule -a matplotlib desktop=^1 state=pseudo_tiled focus=off diff --git a/k-teng/keithley/measure.py b/k-teng/keithley/measure.py deleted file mode 100644 index 38eac9c..0000000 --- a/k-teng/keithley/measure.py +++ /dev/null @@ -1,107 +0,0 @@ -from time import sleep -import numpy as np -from matplotlib import pyplot as plt -import pyvisa - -from .keithley import reset -from ..utility import testing as _testing - -def measure_count(instr, V=True, I=True, count=100, interval=0.05, update_func=None, update_interval=0.5, beep_done=True, verbose=True, testing=False): - """ - Take measurements with inbetween - - @details - Uses the devices overlappedY function to make the measurements asynchronosly - The update_func is optional and only used when I == True and V == True - The update_func does not necessarily get all the values that are measured. To obtain the whole measurement, get them from the device buffers (smua.nvbufferX) - @param instr: pyvisa instrument - @param update_func: Callable that processes the measurements: (index, ival, vval) -> None - @param update_interval: interval at which the update_func is called - """ - f_meas = None - if V and I: - f_meas = "smua.measure.overlappediv(smua.nvbuffer1, smua.nvbuffer2)" - elif V: - f_meas = "smua.measure.overlappedv(smua.nvbuffer1)" - elif I: - f_meas = "smua.measure.overlappedi(smua.nvbuffer1)" - else: - print("I and/or V needs to be set to True") - return - - i = 0 - if not testing: - reset(instr, verbose=verbose) - instr.write(f"smua.measure.count = {count}") - instr.write(f"smua.measure.interval = {interval}") - - # start measurement - instr.write(f"smua.source.output = smua.OUTPUT_ON") - instr.write(f_meas) - - condition = lambda: float(instr.query("print(status.operation.measuring.condition)").strip("\n ")) != 0 - else: - condition = lambda: i < int(float(count) * interval / update_interval) - - sleep(update_interval) - # for live viewing - - query = """if smua.nvbufferX.n > 0 then print(smua.nvbufferX.readings[smua.nvbufferX.n]) else print(0) end""" - # will return 2.0 while measruing - while condition(): - if update_func and V and I: - try: - if not testing: - ival = float(instr.query(query.replace("X", "1")).strip("\n")) - vval = float(instr.query(query.replace("X", "2")).strip("\n")) - else: - ival = _testing.testcurve(i, peak_width=1, amplitude=5e-8) - vval = -_testing.testcurve(i, peak_width=2, amplitude=15) - update_func(i, ival, vval) - except ValueError: - pass - sleep(update_interval) - i += 1 - - if not testing: - instr.write(f"smua.source.output = smua.OUTPUT_OFF") - - if beep_done: - instr.write("beeper.beep(0.3, 1000)") - - -def measure(instr, interval, update_func=None, max_measurements=None, testing=False): - """ - @details: - - Resets the buffers - - Until KeyboardInterrupt: - - Take measurement - - Call update_func - - Wait interval - Uses python's time.sleep() for waiting the interval, which is not very precise. Use measure_count for better precision - You can take the data from the buffer afterwards, using save_csv - @param instr: pyvisa instrument - @param update_func: Callable that processes the measurements: (index, ival, vval) -> None - @param max_measurements : maximum number of measurements. None means infinite - """ - if not testing: - reset(instr, verbose=True) - instr.write("smua.source.output = smua.OUTPUT_ON") - instr.write("format.data = format.ASCII\nformat.asciiprecision = 12") - try: - i = 0 - while max_measurements is None or i < max_measurements: - if testing: - ival = _testing.testcurve(i, peak_width=1, amplitude=5e-8) - vval = -_testing.testcurve(i, peak_width=2, amplitude=15) - else: - ival, vval = tuple(float(v) for v in instr.query("print(smua.measure.iv(smua.nvbuffer1, smua.nvbuffer2))").strip('\n').split('\t')) - if update_func: - update_func(i, ival, vval) - sleep(interval) - i += 1 - except KeyboardInterrupt: - pass - if not testing: - instr.write("smua.source.output = smua.OUTPUT_OFF") - print("Measurement stopped" + " "*50) diff --git a/k-teng/materials b/k-teng/materials deleted file mode 100644 index 36fc873..0000000 --- a/k-teng/materials +++ /dev/null @@ -1,4 +0,0 @@ -pdms -kapton -plastic - diff --git a/k-teng/test.py b/k-teng/test.py deleted file mode 100644 index 7480266..0000000 --- a/k-teng/test.py +++ /dev/null @@ -1,49 +0,0 @@ -readings = [ - 4.974127e-10, -1.418591e-12, -1.573563e-12, -1.728535e-12, -1.704693e-12, - -1.847744e-12, -1.931190e-12, -1.788139e-12, -1.871586e-12, -2.169609e-12, - -2.074242e-12, -1.895428e-12, -1.895428e-12, -1.776218e-12, -1.990795e-12, - -1.788139e-12, -1.811981e-12, -1.657009e-12, -1.573563e-12, -2.396107e-12, - -2.515316e-12, -2.765656e-12, -2.825260e-12, -3.147125e-12, -2.300739e-12, - -2.825260e-12, -3.278255e-12, -5.257130e-12, -6.818771e-12, -8.916855e-12, - -7.712841e-12, 6.437302e-12, -1.142025e-11, -1.206398e-11, -4.649043e-10, - -3.427613e-09, -2.460408e-09, -2.340376e-09, -1.306653e-10, 1.496077e-11, - 2.933741e-11, 1.953280e-09, 8.579970e-10, 9.226799e-12, -1.095533e-11, - -2.508163e-11, -2.776039e-09, -8.686423e-09, 4.935264e-12, 1.246929e-11, - 3.225744e-09, 2.814472e-09, 1.877034e-09, 2.229273e-09, 1.713574e-09, - 8.355618e-10, -4.332781e-10, 5.896091e-11, 5.762577e-11, 8.129537e-09, - 4.044378e-09, 1.771629e-09, 7.849216e-10, 4.098892e-10, 3.390551e-10, - 2.956390e-10, 3.033876e-10, 1.716256e-10, 1.463890e-11, -5.078316e-12, - -6.949902e-12, -8.106232e-12, -6.473065e-12, -4.506111e-12, 4.919767e-11, - 3.052297e-08, 1.161162e-08, -9.892106e-09, -3.613818e-09, -5.004287e-09, - -2.015829e-11, -4.183054e-11, -1.810908e-10, -2.042532e-10, -3.516316e-10, - 5.099773e-11, 1.921976e-08, -1.256589e-08, -4.242897e-10, -1.358986e-12, - -3.445148e-12, -3.838539e-12, -4.184246e-12, -7.402897e-12, -2.840877e-10, - -2.872229e-10, -2.730966e-10, -1.134396e-10, -4.376173e-11, -3.576279e-14 -] -timestamps = [ - 0. , 0.05 , 0.1 , 0.15, 0.2, 0.25, 0.3, 0.35, - 0.4 , 0.45 , 0.5 , 0.55, 0.6, 0.65, 0.7, 0.75, - 0.8 , 0.85 , 0.9 , 0.95, 1. , 1.05, 1.1, 1.15, - 1.2 , 1.25 , 1.3 , 1.35, 1.4, 1.45, 1.5, 1.55, - 1.6 , 1.690891, 1.710923, 1.75, 1.8, 1.85, 1.9, 1.95, - 2. , 2.05 , 2.1 , 2.15, 2.2, 2.25, 2.3, 2.35, - 2.420332, 2.45 , 2.5 , 2.55, 2.6, 2.65, 2.7, 2.75, - 2.820553, 2.890843, 2.910875, 2.95, 3. , 3.05, 3.1, 3.15, - 3.2 , 3.25 , 3.3 , 3.35, 3.4, 3.45, 3.5, 3.55, - 3.6 , 3.65 , 3.7 , 3.75, 3.8, 3.85, 3.9, 3.95, - 4. , 4.05 , 4.1 , 4.15, 4.2, 4.25, 4.3, 4.35, - 4.4 , 4.45 , 4.5 , 4.55, 4.6, 4.65, 4.7, 4.75, - 4.8 , 4.85 , 4.9 , 4.95, ] - -import pandas as pd -import numpy as np -import scipy as sp -import matplotlib.pyplot as plt -df = pd.DataFrame(np.vstack((timestamps, readings)).T) -df.columns = ["Time [s]", "Voltage [V]"] -print(df) -df.to_csv("test.csv", sep=",", header=True, index=False) - -df.plot(x='Time [s]', y="Voltage [V]") -plt.show() - diff --git a/k-teng/testcurve.py b/k-teng/testcurve.py deleted file mode 100644 index 9cbc49b..0000000 --- a/k-teng/testcurve.py +++ /dev/null @@ -1,33 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt - -def testcurve(x, frequency=10, peak_width=2, amplitude=20, bias=0): - # want peak at n*time == frequency - nearest_peak = np.round(x / frequency, 0) - # if not peak at 0 and within peak_width - # print(x, nearest_peak) - if nearest_peak > 0 and abs((x - nearest_peak * frequency)) < peak_width: - # return sin that does one period within 2*peak_width - return amplitude * np.sin(2*np.pi * (x - nearest_peak * frequency - peak_width) / (2*peak_width)) + bias - else: - return bias - - # 0 = pk - width - # 2pi = pk + width - -def baseline(data): - # find the value where the most values with low gradients are closest to - gradients = np.abs(np.gradient(data)) - # consider the values where the absolute gradient is in the bottom 20% - n_gradients = len(data) // 20 - consider_indices = np.argsort(gradients)[:n_gradients] - # of those, only consider values where the value - consider_values = data[consider_indices] - - -xdata = np.arange(0, 100, 0.01) -ydata = np.vectorize(testcurve)(xdata) - - -plt.plot(xdata, ydata) -plt.show() diff --git a/k-teng/utility/testing.py b/k-teng/utility/testing.py deleted file mode 100644 index e2444d6..0000000 --- a/k-teng/utility/testing.py +++ /dev/null @@ -1,38 +0,0 @@ - -import pandas as pd -import numpy as np - -def collect_buffer(instr, buffer_nr=1): - """ - Get the buffer as 2D - np.array - @param instr : pyvisa instrument - @param buffer_nr : 1 or 2, for smua.nvbuffer1 or 2 - @returns 2D numpy array: - i - ith reading: - 0: timestamps - 1: readings - """ - if buffer_nr == 2: buffername = "smua.nvbuffer2" - else: buffername = "smua.nvbuffer1" - # instr.write("format.data = format.DREAL\nformat.byteorder = format.LITTLEENDIAN") - # buffer = instr.query_binary_values(f"printbuffer(1, {buffername}.n, {buffername})", datatype='d', container=np.array) - instr.write("format.data = format.ASCII\nformat.asciiprecision = 7") - timestamps = instr.query_ascii_values(f"printbuffer(1, {buffername}.n, {buffername}.timestamps)", container=np.array) - readings = instr.query_ascii_values(f"printbuffer(1, {buffername}.n, {buffername}.readings)", container=np.array) - print(f"readings: {readings}, \ntimestamps: {timestamps}") - buffer = np.vstack((timestamps, readings)).T - return buffer - - -def testcurve(x, frequency=10, peak_width=2, amplitude=20, bias=0): - # want peak at n*time == frequency - nearest_peak = np.round(x / frequency, 0) - # if not peak at 0 and within peak_width - if nearest_peak > 0 and abs((x - nearest_peak * frequency)) < peak_width: - # return sin that does one period within 2*peak_width - return amplitude * np.sin(2*np.pi * (x - nearest_peak * frequency - peak_width) / (2*peak_width)) + bias - else: - return bias - - # 0 = pk - width - # 2pi = pk + width diff --git a/k-teng/__init__.py b/m_teng/__init__.py similarity index 100% rename from k-teng/__init__.py rename to m_teng/__init__.py diff --git a/k-teng/keithley/__init__.py b/m_teng/backends/__init__.py similarity index 100% rename from k-teng/keithley/__init__.py rename to m_teng/backends/__init__.py diff --git a/k-teng/utility/__init__.py b/m_teng/backends/arduino/__init__.py similarity index 100% rename from k-teng/utility/__init__.py rename to m_teng/backends/arduino/__init__.py diff --git a/m_teng/backends/arduino/arduino.py b/m_teng/backends/arduino/arduino.py new file mode 100644 index 0000000..1073ae8 --- /dev/null +++ b/m_teng/backends/arduino/arduino.py @@ -0,0 +1,123 @@ +import bleak as b +import asyncio +import numpy as np + +TARGET_NAME = "ArduinoTENG" + +# GATT service and characteristics UUIDs +TENG_SUUID = "00010000-9a74-4b30-9361-4a16ec09930f" +TENG_STATUS_CUUID = "00010001-9a74-4b30-9361-4a16ec09930f" +TENG_COMMAND_CUUID = "00010002-9a74-4b30-9361-4a16ec09930f" +TENG_READING_CUUID = "00010003-9a74-4b30-9361-4a16ec09930f" +TENG_SETTING_INTERVAL_CUUID = "00010004-9a74-4b30-9361-4a16ec09930f" + +TENG_COMMANDS = { + "NOOP": int(0).to_bytes(1), + "MEASURE_BASELINE": int(1).to_bytes(1), +} +TENG_STATUS = ["ERROR", "BUSY", "WAIT_CONNECT", "MEASURING_BASELINE", "READING"] + +# TODO save measurements on device buffer, transfer later + + +# wrapper for global variable +class Buffer: + def __init__(self): + self.data = None +_buffer = Buffer() + +def teng_status_callback(characteristic, data): + value = int.from_bytes(data, byteorder="big", signed=False) + if 0 <= value and value < len(TENG_STATUS): + print(f"Status change: {TENG_STATUS[value]}") + else: + print(f"Status change (invalid): status={value}") + + +def disconnect_callback(client): + raise Exception(f"The bluetooth device {client.name} was disconnected") + + +async def init_arduino_async(n_tries: int=5) -> b.BleakClient: + n_try = 0 + if n_tries <= 0: n_tries = "inf" + try: + target_device = None + while target_device is None and (n_tries == "inf" or n_try < n_tries): + print(f"Searching for bluetooth device '{TARGET_NAME}' ({n_try}/{n_tries})", end="\r") + devices = await b.BleakScanner.discover(return_adv=True) + # print(devices) + for adr, (device, adv_data) in devices.items(): + if device.name == TARGET_NAME: + # print(adv_data) + target_device = device + break + n_try += 1 + if target_device is None: + raise Exception(f"Could not find bluetooth device 'ArduinoTENG'") + # print(f"Found target device: {target_device.name}: {target_device.metadata}, {target_device.details}") + # print(target_device.name, target_device.details) + client = b.BleakClient(target_device, disconnect_callback=disconnect_callback) + return client + except asyncio.exceptions.CancelledError: + raise Exception(f"Cancelled") + + +def init(beep_success=True, n_tries: int=5) -> b.BleakClient: + """ + Connect to the arduino + @returns: BleakClient + """ + client = asyncio.run(init_arduino_async(n_tries=n_tries)) + if beep_success: beep(client) + return client + + +def set_interval(client, interval: float): + """ + Set the measurement interval + @param interval: interval in seconds + """ + interval = int(interval * 1000) # convert to ms for arduinos delay) + await client.write_gatt_char(TENG_SETTING_INTERVAL_CUUID, interval.to_bytes(2, byteorder=LITTLEENDIAN, signed=False)) + + +# async def main(): +# for service in client.services: +# print(f"Service: {service.uuid}: {service.description}") +# for c in service.characteristics: +# print(f"\t{c.uuid}: {c.properties}, {c.descriptors}") +# teng_status = client.services.get_characteristic(TENG_STATUS_CUUID) +# teng_command = client.services.get_characteristic(TENG_COMMAND_CUUID) +# teng_reading = client.services.get_characteristic(TENG_READING_CUUID) +# client.start_notify(teng_status, teng_status_callback) + +# await client.write_gatt_char(teng_command, TENG_COMMANDS["NOOP"]) +# await asyncio.sleep(5) +# await client.write_gatt_char(teng_command, TENG_COMMANDS["MEASURE_BASELINE"]) + +# while client.is_connected: +# data = await client.read_gatt_char(teng_reading) + + +# value = int.from_bytes(data, byteorder="little", signed=False) +# print(f"Reading: {value}") +# await asyncio.sleep(0.5) +# except KeyboardInterrupt: +# pass +# except asyncio.exceptions.CancelledError: +# pass +# print("Disconnected") + + +def collect_buffer(instr, buffer_nr=1): + """ + @param buffer_nr: 1 -> current, 2 -> voltage + """ + assert(buffer_nr in (1, 2)) + return np.vstack((_buffer.data[:,0], _buffer._data[:,buffer_nr])).T + + +def beep(client): + # TODO connect beeper to arduino? + print(beep) diff --git a/m_teng/backends/arduino/measure.py b/m_teng/backends/arduino/measure.py new file mode 100644 index 0000000..eb8cef5 --- /dev/null +++ b/m_teng/backends/arduino/measure.py @@ -0,0 +1,59 @@ +import bleak as b +import numpy as np + +import asyncio +import datetime + +from .arduino.arduino import beep, set_interval, TENG_READING_CUUID, _buffer + + +# equivalent to internal keithley buffer: write to this value and collect afterwards + +def measure_count(client, count=100, interval=0.05, update_func=None, update_interval=0.5, beep_done=True, verbose=True, testing=False): + global _buffer + _buffer.data = np.zeros((count, 3)) + i = 0 + t_start = datetime.datetime.now() + + async def add_buffer.data(client): + if i >= count: return + _buffer.data[i][0] = float((datetime.datetime.now() - t_start).microseconds) / 1000 + reading = await client.read_gatt_char(TENG_READING_CUUID) + _buffer.data[i][2] = int.from_bytes(reading, byteorder=LITTLEENDIAN, signed=False). + i += 1 + + set_interval(client, interval) + # TODO check if notify works when the same value is written again + client.start_notify(TENG_READING_CUUID, add_buffer.data) + while i < count: + asyncio.sleep(update_interval) + if update_func is not None: # assume an update has occured + update_func(i, 0, _buffer.data[i, 2]) + if beep_done: beep(client) + + +def measure(client, interval, update_func=None, max_measurements=None, testing=False): + global _buffer + _buffer.data = np.zeros((count, 3)) + i = 0 + t_start = datetime.datetime.now() + + async def add_buffer.data(client): + if i >= count: return + _buffer.data[i][0] = datetime.datetime.now() - t_start + vval = await client.read_gatt_char(TENG_READING_CUUID) + vval = int.from_bytes(reading, byteorder=LITTLEENDIAN, signed=False). + _buffer.data[i][2] = vval + if update_func: + update_func(i, 0, vval) + i += 1 + + set_interval(client, interval) + client.start_notify(TENG_READING_CUUID, add_buffer.data) + try: + while max_measurements is None or i < max_measurements: + asyncio.sleep(interval / 2) # + except asyncio.exceptions.CancelledError: + pass + print("Measurement stopped" + " "*50) + diff --git a/m_teng/backends/keithley/__init__.py b/m_teng/backends/keithley/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/k-teng/keithley/keithley.py b/m_teng/backends/keithley/keithley.py similarity index 56% rename from k-teng/keithley/keithley.py rename to m_teng/backends/keithley/keithley.py index 4c86d69..6218803 100644 --- a/k-teng/keithley/keithley.py +++ b/m_teng/backends/keithley/keithley.py @@ -1,20 +1,19 @@ import pyvisa import numpy as np +import pkg_resources """ Utility """ -script_dir = "../scripts/" + scripts = { - "buffer_reset": "buffer_reset.lua", - "smua_reset": "smua_reset.lua", + "buffer_reset": pkg_resources.resource_filename("m_teng", "keithley_scripts/buffer_reset.lua"), + "smua_reset": pkg_resources.resource_filename("m_teng", "keithley_scripts/smua_reset.lua"), } -for key,val in scripts.items(): - scripts[key] = script_dir + scripts[key] -def init_keithley(beep_success=True): +def init(beep_success=True): rm = pyvisa.ResourceManager('@py') resources = rm.list_resources() if len(resources) < 1: @@ -52,6 +51,15 @@ def reset(instr, verbose=False): run_lua(instr, scripts["smua_reset"], verbose=verbose) run_lua(instr, scripts["buffer_reset"], verbose=verbose) +def get_buffer_name(buffer_nr: int): + if buffer_nr == 2: return "smua.nvbuffer2" + elif buffer_nr == 1: return "smua.nvbuffer1" + raise ValueError(f"Invalid buffer_nr: {buffer_nr}") + +def get_buffer_size(instr, buffer_nr=1): + n = instr.query(f"print({get_buffer_name(buffer_nr)}.n)").strip("\n") + return int(float(n)) + def collect_buffer(instr, buffer_nr=1, verbose=False): """ @@ -63,8 +71,7 @@ def collect_buffer(instr, buffer_nr=1, verbose=False): 0: timestamps 1: readings """ - if buffer_nr == 2: buffername = "smua.nvbuffer2" - else: buffername = "smua.nvbuffer1" + buffername = get_buffer_name(buffer_nr) # instr.write("format.data = format.DREAL\nformat.byteorder = format.LITTLEENDIAN") # buffer = instr.query_binary_values(f"printbuffer(1, {buffername}.n, {buffername})", datatype='d', container=np.array) instr.write("format.data = format.ASCII\nformat.asciiprecision = 7") @@ -75,3 +82,30 @@ def collect_buffer(instr, buffer_nr=1, verbose=False): buffer = np.vstack((timestamps, readings)).T return buffer + + +def collect_buffer_range(instr, range_=(1, -1), buffer_nr=1, verbose=False): + """ + Get the buffer as 2D - np.array + @param instr : pyvisa instrument + @param buffer_nr : 1 or 2, for smua.nvbuffer1 or 2 + @returns 2D numpy array: + i - ith reading: + 0: timestamps + 1: readings + """ + buffername = get_buffer_name(buffer_nr) + # instr.write("format.data = format.DREAL\nformat.byteorder = format.LITTLEENDIAN") + # buffer = instr.query_binary_values(f"printbuffer(1, {buffername}.n, {buffername})", datatype='d', container=np.array) + if range_[1] == -1: + range_ = (range_[0], f"{buffername}.n") + instr.write("format.data = format.ASCII\nformat.asciiprecision = 7") + timestamps = instr.query_ascii_values(f"printbuffer({range_[0]}, {range_[1]}, {buffername}.timestamps)", container=np.array) + readings = instr.query_ascii_values(f"printbuffer({range_[0]}, {range_[1]}, {buffername}.readings)", container=np.array) + if verbose: + print(f"readings from {buffername}: {readings}, \ntimestamps: {timestamps}") + buffer = np.vstack((timestamps, readings)).T + return buffer + + + diff --git a/m_teng/backends/keithley/measure.py b/m_teng/backends/keithley/measure.py new file mode 100644 index 0000000..30531d2 --- /dev/null +++ b/m_teng/backends/keithley/measure.py @@ -0,0 +1,93 @@ +from time import sleep +import numpy as np +from matplotlib import pyplot as plt +import pyvisa + +from .keithley import reset +from ..utility import testing as _testing + +def measure_count(instr, count=100, interval=0.05, update_func=None, update_interval=0.5, beep_done=True, verbose=True): + """ + Take measurements with inbetween + + @details + Uses the devices overlappedY function to make the measurements asynchronosly + The update_func is optional and only used when I == True and V == True + The update_func does not necessarily get all the values that are measured. To obtain the whole measurement, get them from the device buffers (smua.nvbufferX) + @param instr: pyvisa instrument + @param update_func: Callable that processes the measurements: (index, ival, vval) -> None + @param update_interval: interval at which the update_func is called + """ + f_meas = "smua.measure.overlappediv(smua.nvbuffer1, smua.nvbuffer2)" + # if V and I: + # elif V: + # f_meas = "smua.measure.overlappedv(smua.nvbuffer1)" + # elif I: + # f_meas = "smua.measure.overlappedi(smua.nvbuffer1)" + # else: + # print("I and/or V needs to be set to True") + # return + + i = 0 + reset(instr, verbose=verbose) + instr.write(f"smua.measure.count = {count}") + instr.write(f"smua.measure.interval = {interval}") + + # start measurement + instr.write(f"smua.source.output = smua.OUTPUT_ON") + instr.write(f_meas) + + sleep(update_interval) + # for live viewing + query = """if smua.nvbufferX.n > 0 then print(smua.nvbufferX.readings[smua.nvbufferX.n]) else print(0) end""" + + # will return 2.0 while measruing + while float(instr.query("print(status.operation.measuring.condition)").strip("\n ")) != 0: + if update_func: + try: + ival = float(instr.query(query.replace("X", "1")).strip("\n")) + vval = float(instr.query(query.replace("X", "2")).strip("\n")) + update_func(i, ival, vval) + except ValueError as e: + if i != 0: + pass + else: + print(f"measure_count: ValueError: {e}") + sleep(update_interval) + i += 1 + + instr.write(f"smua.source.output = smua.OUTPUT_OFF") + + if beep_done: + instr.write("beeper.beep(0.3, 1000)") + + +def measure(instr, interval, update_func=None, max_measurements=None): + """ + @details: + - Resets the buffers + - Until KeyboardInterrupt: + - Take measurement + - Call update_func + - Wait interval + Uses python's time.sleep() for waiting the interval, which is not very precise. Use measure_count for better precision + You can take the data from the buffer afterwards, using save_csv + @param instr: pyvisa instrument + @param update_func: Callable that processes the measurements: (index, ival, vval) -> None + @param max_measurements : maximum number of measurements. None means infinite + """ + reset(instr, verbose=True) + instr.write("smua.source.output = smua.OUTPUT_ON") + instr.write("format.data = format.ASCII\nformat.asciiprecision = 12") + try: + i = 0 + while max_measurements is None or i < max_measurements: + ival, vval = tuple(float(v) for v in instr.query("print(smua.measure.iv(smua.nvbuffer1, smua.nvbuffer2))").strip('\n').split('\t')) + if update_func: + update_func(i, ival, vval) + sleep(interval) + i += 1 + except KeyboardInterrupt: + pass + instr.write("smua.source.output = smua.OUTPUT_OFF") + print("Measurement stopped" + " "*50) diff --git a/scripts/buffer_reset.lua b/m_teng/keithley_scripts/buffer_reset.lua similarity index 100% rename from scripts/buffer_reset.lua rename to m_teng/keithley_scripts/buffer_reset.lua diff --git a/scripts/smua_reset.lua b/m_teng/keithley_scripts/smua_reset.lua similarity index 100% rename from scripts/smua_reset.lua rename to m_teng/keithley_scripts/smua_reset.lua diff --git a/k-teng/k_teng_interactive.py b/m_teng/m_teng_interactive.py similarity index 67% rename from k-teng/k_teng_interactive.py rename to m_teng/m_teng_interactive.py index 7714e46..53a6d96 100644 --- a/k-teng/k_teng_interactive.py +++ b/m_teng/m_teng_interactive.py @@ -1,6 +1,6 @@ """ run this before using this library: -ipython -i k_teng_interactive.py +ipython -i m_teng_interactive.py always records iv-t curves i-data -> smua.nvbuffer1 @@ -18,21 +18,54 @@ from os import path, makedirs import pickle as pkl import json +import argparse + + if __name__ == "__main__": + import sys if __package__ is None: # make relative imports work as described here: https://peps.python.org/pep-0366/#proposed-change - __package__ = "k-teng" - import sys + __package__ = "m_teng" from os import path filepath = path.realpath(path.abspath(__file__)) sys.path.insert(0, path.dirname(path.dirname(filepath))) + parser = argparse.ArgumentParser( + prog="m-teng", + description="measure triboelectric nanogenerator output using a Keithley SMU or an Arduino", + ) + backend_group = parser.add_mutually_exclusive_group(required=True) + backend_group.add_argument("-k", "--keithley", action="store_true") + backend_group.add_argument("-a", "--arduino", action="store_true") + backend_group.add_argument("-t", "--testing", action='store_true') + parser.add_argument("-c", "--config", action="store", help="alternate path to config file") + args = vars(parser.parse_args()) + + i = 1 + while i < len(sys.argv): + if args["keithley"]: + import m_teng.backends.keithley.keithley as _backend + import m_teng.backends.keithley.measure as _measure + elif args["arduino"]: + import m_teng.backends.arduino.arduino as _backend + import m_teng.backends.arduino.measure as _measure + elif args["testing"]: + import m_teng.backends.testing.testing as _backend + import m_teng.backends.testing.measure as _measure + elif sys.argv[i] in ["-c", "--config"]: + if i+1 < len(sys.argv): + config_path = sys.argv[i+1] + else: + print("-c requires an extra argument: path of config file") + i += 1 + i += 1 -from .keithley import keithley as _keithley -from .keithley.measure import measure_count as _measure_count, measure as _measure -from .utility import data as _data -from .utility.data import load_dataframe -from .utility import file_io +from m_teng.utility import data as _data +from m_teng.utility.data import load_dataframe +from m_teng.utility import file_io +from m_teng.update_funcs import _Monitor, _ModelPredict, _update_print + +config_path = path.expanduser("~/.config/k-teng.json") _runtime_vars = { "last-measurement": "" @@ -44,66 +77,35 @@ settings = { "interval": 0.02, "beep": True, } -config_path = path.expanduser("~/.config/k-teng.json") test = False -# global variable for the instrument returned by pyvisa -k = None +# global variable for the instrument/client returned by pyvisa/bleak +dev = None - -def _update_print(i, ival, vval): - print(f"n = {i:5d}, I = {ival: .12f} A, U = {vval: .5f} V" + " "*10, end='\r') - -class _Monitor: +def monitor_predict(model_dir: str, count=5000, interval=settings["interval"], max_points_shown=160): """ - Monitor v and i data + Take measurements in and predict with a machine learning model """ - def __init__(self, max_points_shown=None, use_print=False): - self.max_points_shown = max_points_shown - self.use_print = use_print - self.index = [] - self.vdata = [] - self.idata = [] - - plt.ion() - self.fig1, (self.vax, self.iax) = plt.subplots(2, 1, figsize=(8, 5)) - - self.vline, = self.vax.plot(self.index, self.vdata, color="g") - self.vax.set_ylabel("Voltage [V]") - self.vax.grid(True) - - self.iline, = self.iax.plot(self.index, self.idata, color="m") - self.iax.set_ylabel("Current [A]") - self.iax.grid(True) - - def update(self, i, ival, vval): - if self.use_print: - _update_print(i, ival, vval) - self.index.append(i) - self.idata.append(ival) - self.vdata.append(vval) - # update data - self.iline.set_xdata(self.index) - self.iline.set_ydata(self.idata) - self.vline.set_xdata(self.index) - self.vline.set_ydata(self.vdata) - # recalculate limits and set them for the view - self.iax.relim() - self.vax.relim() - if self.max_points_shown and i > self.max_points_shown: - self.iax.set_xlim(i - self.max_points_shown, i) - self.vax.set_xlim(i - self.max_points_shown, i) - self.iax.autoscale_view() - self.vax.autoscale_view() - # update plot - self.fig1.canvas.draw() - self.fig1.canvas.flush_events() - - def __del__(self): - plt.close(self.fig1) + model_predict = _ModelPredict(dev, model_dir) + plt_monitor = _Monitor(max_points_shown, use_print=False) + skip_n = 0 + def update(i, ival, vval): + plt_monitor.update(i, ival, vval) + if skip_n % 10 == 0: + model_predict.update(i, ival, vval) + skip_n += 1 + print(f"Starting measurement with:\n\tinterval = {interval}s\nSave the data using 'save_csv()' afterwards.") + try: + _measure.measure_count(dev, V=True, I=True, count=count, interval=interval, beep_done=False, verbose=False, update_func=update, update_interval=0.1) + except KeyboardInterrupt: + if args["keithley"]: + dev.write(f"smua.source.output = smua.OUTPUT_OFF") + print("Monitoring cancelled, measurement might still continue" + " "*50) + else: + print("Measurement finished" + " "*50) def monitor_count(count=5000, interval=settings["interval"], max_points_shown=160): """ @@ -123,10 +125,10 @@ def monitor_count(count=5000, interval=settings["interval"], max_points_shown=16 print(f"Starting measurement with:\n\tinterval = {interval}s\nSave the data using 'save_csv()' afterwards.") try: - _measure_count(k, V=True, I=True, count=count, interval=interval, beep_done=False, verbose=False, update_func=update_func, update_interval=0.05, testing=test) + _measure.measure_count(dev, V=True, I=True, count=count, interval=interval, beep_done=False, verbose=False, update_func=update_func, update_interval=0.05) except KeyboardInterrupt: - if not test: - k.write(f"smua.source.output = smua.OUTPUT_OFF") + if args["keithley"]: + dev.write(f"smua.source.output = smua.OUTPUT_OFF") print("Monitoring cancelled, measurement might still continue" + " "*50) else: print("Measurement finished" + " "*50) @@ -147,10 +149,10 @@ def measure_count(count=5000, interval=settings["interval"]): print(f"Starting measurement with:\n\tinterval = {interval}s\nSave the data using 'save_csv()' afterwards.") try: - _measure_count(k, V=True, I=True, count=count, interval=interval, beep_done=False, verbose=False, update_func=update_func, update_interval=0.05, testing=test) + _measure.measure_count(dev, count=count, interval=interval, beep_done=False, verbose=False, update_func=update_func, update_interval=0.05) except KeyboardInterrupt: - if not test: - k.write(f"smua.source.output = smua.OUTPUT_OFF") + if args["keithley"]: + dev.write(f"smua.source.output = smua.OUTPUT_OFF") print("Monitoring cancelled, measurement might still continue" + " "*50) else: print("Measurement finished" + " "*50) @@ -176,7 +178,7 @@ def monitor(interval=settings["interval"], max_measurements=None, max_points_sho print(f"Starting measurement with:\n\tinterval = {interval}s\nUse to stop. Save the data using 'save_csv()' afterwards.") plt_monitor = _Monitor(use_print=True, max_points_shown=max_points_shown) update_func = plt_monitor.update - _measure(k, interval=interval, max_measurements=max_measurements, update_func=update_func, testing=test) + _measure.measure(dev, interval=interval, max_measurements=max_measurements, update_func=update_func) def measure(interval=settings["interval"], max_measurements=None): @@ -195,7 +197,7 @@ def measure(interval=settings["interval"], max_measurements=None): _runtime_vars["last_measurement"] = dtime.now().isoformat() print(f"Starting measurement with:\n\tinterval = {interval}s\nUse to stop. Save the data using 'save_csv()' afterwards.") update_func = _update_print - _measure(k, interval=interval, max_measurements=max_measurements, update_func=update_func, testing=test) + _measure.measure(dev, interval=interval, max_measurements=max_measurements, update_func=update_func) def repeat(measure_func: callable, count: int, repeat_delay=0): @@ -221,7 +223,7 @@ def repeat(measure_func: callable, count: int, repeat_delay=0): sleep(repeat_delay) except KeyboardInterrupt: pass - if settings["beep"]: k.write("beeper.beep(0.3, 1000)") + if settings["beep"]: _backend.beep() def get_dataframe(): @@ -229,21 +231,14 @@ def get_dataframe(): Get a pandas dataframe from the data in smua.nvbuffer1 and smua.nvbuffer2 """ global k, settings, _runtime_vars - if test: - timestamps = np.arange(0, 50, 0.01) - ydata = np.array([testing.testcurve(t, amplitude=15, peak_width=2) for t in timestamps]) - ibuffer = np.vstack((timestamps, ydata)).T - - ydata = np.array([-testing.testcurve(t, amplitude=5e-8, peak_width=1) for t in timestamps]) - vbuffer = np.vstack((timestamps, ydata)).T - else: - ibuffer = _keithley.collect_buffer(k, 1) - vbuffer = _keithley.collect_buffer(k, 2) + ibuffer = _backend.collect_buffer(dev, 1) + vbuffer = _backend.collect_buffer(dev, 2) df = _data.buffers2dataframe(ibuffer, vbuffer) df.basename = file_io.get_next_filename(settings["name"], settings["datadir"]) df.name = f"{df.basename} @ {_runtime_vars['last-measurement']}" return df + def save_csv(): """ Saves the contents of nvbuffer1 as .csv @@ -267,6 +262,7 @@ def save_pickle(): df.to_pickle(filename) print(f"Saved as '{filename}'") + def run_script(script_path): """ Run a lua script on the Keithley device @@ -276,7 +272,7 @@ def run_script(script_path): if test: print("run_script: Test mode enabled, ignoring call to run_script") else: - _keithley.run_lua(k, script_path=script_path) + _keithley.run_lua(dev, script_path=script_path) def set(setting, value): @@ -333,13 +329,13 @@ Run 'help("topic")' to see more information on a topic""") Functions: name("") - short for set("name", "") set("setting", value) - set a setting to a value - save_settings() - store the settings as "k-teng.conf" in the working directory + save_settings() - store the settings as "m-teng.json" in the working directory load_settings() - load settings from a file The global variable 'config_path' determines the path used by save/load_settings. Use -c '' to set another path. The serach path is: - /k-teng.json - $XDG_CONFIG_HOME/k-teng.json - ~/.config/k-teng.json + /m-teng.json + $XDG_CONFIG_HOME/m-teng.json + ~/.config/m-teng.json """) elif topic == "imports": print("""Imports: @@ -349,7 +345,7 @@ Functions: os.path """) elif topic == "device": print("""Device: - The opened pyvisa resource (Keithley device) is the global variable 'k'. + The opened pyvisa resource (deveithley device) is the global variable 'k'. You can interact using pyvisa functions, such as k.write("command"), k.query("command") etc. to interact with the device.""") else: @@ -357,37 +353,26 @@ Functions: def init(): - global k, settings, test, config_path - print(r""" ____ __. ______________________ _______ ________ -| |/ _| \__ ___/\_ _____/ \ \ / _____/ -| < ______ | | | __)_ / | \ / \ ___ -| | \ /_____/ | | | \/ | \\ \_\ \ -|____|__ \ |____| /_______ /\____|__ / \______ / - \/ \/ \/ \/ 1.1 + global dev, settings, config_path + print(r""" ______________________ _______ ________ + _____ \__ ___/\_ _____/ \ \ / _____/ + / \ ______| | | __)_ / | \ / \ ___ +| Y Y \/_____/| | | \/ | \\ \_\ \ +|__|_| / |____| /_______ /\____|__ / \______ / + \/ \/ \/ \/ 1.2 Interactive Shell for TENG measurements with Keithley 2600B --- Enter 'help()' for a list of commands""") from os import environ - if path.isfile("k-teng.json"): - config_path = "k-teng.json" + if path.isfile("m-teng.json"): + config_path = "m-teng.json" elif 'XDG_CONFIG_HOME' in environ.keys(): - # and path.isfile(environ["XDG_CONFIG_HOME"] + "/k-teng.json"): - config_path = environ["XDG_CONFIG_HOME"] + "/k-teng.json" + # and path.isfile(environ["XDG_CONFIG_HOME"] + "/m-teng.json"): + config_path = environ["XDG_CONFIG_HOME"] + "/m-teng.json" else: - config_path = path.expanduser("~/.config/k-teng.json") - - from sys import argv - i = 1 - while i < len(argv): - if argv[i] in ["-t", "--test"]: - test = True - elif argv[i] in ["-c", "--config"]: - if i+1 < len(argv): - config_path = argv[i+1] - else: - print("-c requires an extra argument: path of config file") - i += 1 - i += 1 + config_path = path.expanduser("~/.config/m-teng.json") + if args["config"]: + config_path = args["config"] if not path.isdir(path.dirname(config_path)): @@ -399,15 +384,11 @@ Enter 'help()' for a list of commands""") if not path.isdir(settings["datadir"]): makedirs(settings["datadir"]) - if not test: - from .keithley.keithley import init_keithley - try: - k = init_keithley(beep_success=settings["beep"]) - except Exception as e: - print(e) - exit() - else: - print("Running in test mode, device will not be connected.") + try: + dev = _backend.init(beep_success=settings["beep"]) + except Exception as e: + print(e) + exit(1) if __name__ == "__main__": diff --git a/m_teng/update_funcs.py b/m_teng/update_funcs.py new file mode 100644 index 0000000..8906b29 --- /dev/null +++ b/m_teng/update_funcs.py @@ -0,0 +1,123 @@ +import matplotlib.pyplot as plt +import numpy as np + +import torch + +from teng_ml.util import model_io as mio +from teng_ml.util.settings import MLSettings +from teng_ml.util.split import DataSplitter + +from m_teng.backends.keithley import keithley + +def _update_print(i, ival, vval): + print(f"n = {i:5d}, I = {ival: .12f} A, U = {vval: .5f} V" + " "*10, end='\r') + +class _Monitor: + """ + Monitor v and i data + """ + def __init__(self, max_points_shown=None, use_print=False): + self.max_points_shown = max_points_shown + self.use_print = use_print + self.index = [] + self.vdata = [] + self.idata = [] + + plt.ion() + self.fig1, (self.vax, self.iax) = plt.subplots(2, 1, figsize=(8, 5)) + + self.vline, = self.vax.plot(self.index, self.vdata, color="g") + self.vax.set_ylabel("Voltage [V]") + self.vax.grid(True) + + self.iline, = self.iax.plot(self.index, self.idata, color="m") + self.iax.set_ylabel("Current [A]") + self.iax.grid(True) + + def update(self, i, ival, vval): + if self.use_print: + _update_print(i, ival, vval) + self.index.append(i) + self.idata.append(ival) + self.vdata.append(vval) + # update data + self.iline.set_xdata(self.index) + self.iline.set_ydata(self.idata) + self.vline.set_xdata(self.index) + self.vline.set_ydata(self.vdata) + # recalculate limits and set them for the view + self.iax.relim() + self.vax.relim() + if self.max_points_shown and i > self.max_points_shown: + self.iax.set_xlim(i - self.max_points_shown, i) + self.vax.set_xlim(i - self.max_points_shown, i) + self.iax.autoscale_view() + self.vax.autoscale_view() + # update plot + self.fig1.canvas.draw() + self.fig1.canvas.flush_events() + + def __del__(self): + plt.close(self.fig1) + + +class _ModelPredict: + colors = ["red", "green", "purple", "blue", "orange", "grey", "cyan"] + def __init__(self, instr, model_dir): + """ + @param model_dir: directory where model.plk and settings.pkl are stored + + Predict the values that are currently being recorded + @details: + Load the model and model settings from model dir + Wait until the number of recoreded points is >= the size of the models DataSplitter + Collect the data from the keithley, apply the transforms and predict the label with the model + Shows the prediction with a bar plot + """ + self.instr = instr + self.model = mio.load_model(model_dir) + self.model_settings: MLSettings = mio.load_settings(model_dir) + if type(self.model_settings.splitter) == DataSplitter: + self.data_length = self.model_settings.splitter.split_size + else: + self.data_length = 200 + + plt.ion() + self.fig1, (self.ax) = plt.subplots(1, 1, figsize=(8, 5)) + + self.bar_cont = self.ax.bar(self.model_settings.labels.get_labels(), [ 1 for _ in range(len(self.model_settings.labels))]) + self.ax.set_ylabel("Prediction") + self.ax.grid(True) + + def update(self, i, ival, vval): + buffer_size = keithley.get_buffer_size(self.instr, buffer_nr=1) + if buffer_size <= self.data_length: + print(f"ModelPredict.update: buffer_size={buffer_size} < {self.data_length}") + return + else: + ibuffer = keithley.collect_buffer_range(self.instr, (buffer_size-self.data_length, buffer_size), buffer_nr=1) + vbuffer = keithley.collect_buffer_range(self.instr, (buffer_size-self.data_length, buffer_size), buffer_nr=2) + if self.model_settings.num_features == 1: # model uses only voltage + data = np.vstack((ibuffer[:,0], ibuffer[:,1], vbuffer[:,1])).T + # print(f"data.shape:", data.shape) + else: + raise NotImplementedError(f"Cant handle models with num_features != 1 yet") + for t in self.model_settings.transforms: + data = t(data) + data = np.reshape(data[:,2], (1, -1, 1)) # batch_size, seq, features + with torch.no_grad(): + x = torch.FloatTensor(data) # select voltage data, without timestamps + # print(x.shape) + + prediction = self.model(x) # (batch_size, label-predictions) + prediction = torch.nn.functional.softmax(prediction) # TODO remove when softmax is already applied by model + predicted = torch.argmax(prediction, dim=1, keepdim=False) # -> [ label_indices ] + # print(f"raw={prediction[0]}, predicted_index={predicted[0}") + label = self.model_settings.labels[predicted[0]] + # print(f"-> label={label}") + + self.bar_cont.remove() + self.bar_cont = self.ax.bar(self.model_settings.labels.get_labels(), prediction[0], color=_ModelPredict.colors[:len(self.model_settings.labels)]) + # update plot + self.fig1.canvas.draw() + self.fig1.canvas.flush_events() diff --git a/m_teng/utility/__init__.py b/m_teng/utility/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/k-teng/utility/data.py b/m_teng/utility/data.py similarity index 100% rename from k-teng/utility/data.py rename to m_teng/utility/data.py diff --git a/k-teng/utility/file_io.py b/m_teng/utility/file_io.py similarity index 100% rename from k-teng/utility/file_io.py rename to m_teng/utility/file_io.py diff --git a/m_teng/utility/testing.py b/m_teng/utility/testing.py new file mode 100644 index 0000000..c4e9d79 --- /dev/null +++ b/m_teng/utility/testing.py @@ -0,0 +1,17 @@ +import numpy as np + +def testcurve(x, frequency=10, peak_width=2, amplitude=20, bias=0): + # 0 = pk - width + # 2pi = pk + width + # want peak at n*time == frequency + nearest_peak = np.round(x / frequency, 0) + # if not peak at 0 and within peak_width + if nearest_peak > 0 and np.abs((x - nearest_peak * frequency)) < peak_width: + # return sin that does one period within 2*peak_width + return amplitude * np.sin(2*np.pi * (x - nearest_peak * frequency - peak_width) / (2*peak_width)) + bias + else: + return bias + +def get_testcurve(frequency=10, peak_width=2, amplitude=20, bias=0): + return np.vectorize(lambda x: testcurve(x, frequency=frequency, peak_width=peak_width, amplitude=amplitude, bias=bias)) + diff --git a/measurement.ipynb b/measurement.ipynb deleted file mode 100644 index 1242e99..0000000 --- a/measurement.ipynb +++ /dev/null @@ -1,419 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "d1bb781f-f286-44cf-a3e3-181e06281487", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'keithley'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[1], line 12\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mmatplotlib\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m pyplot \u001b[38;5;28;01mas\u001b[39;00m plt\n\u001b[1;32m 11\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mnumpy\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mnp\u001b[39;00m\n\u001b[0;32m---> 12\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mkeithley\u001b[39;00m\n\u001b[1;32m 13\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mfile_io\u001b[39;00m\n\u001b[1;32m 14\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mmeasure\u001b[39;00m\n", - "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'keithley'" - ] - } - ], - "source": [ - "\"\"\"\n", - "INIT:connect to keithley\n", - "- has to be run before anything else\n", - "- the 'keithley' variable represents the device\n", - "- use keithley.write(), keithley.query(), etc to interact\n", - "\"\"\"\n", - "\n", - "import pyvisa\n", - "from time import sleep\n", - "from matplotlib import pyplot as plt\n", - "import numpy as np\n", - "import keithley\n", - "import file_io\n", - "import measure\n", - "\n", - "keithley = keithley.init_keithley()\n", - "keithley.write(\"format.data = format.ASCII\")\n", - "print(keithley.query(\"*IDN?\"))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "52743b4a-7e0c-4fb5-ace4-4328e5e18940", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "measure.measure_count(keithley, count=100, interval=0.05, beep_done=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "c6a08ceb-1fe9-4517-af74-40a5006de1e8", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "67" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Task exception was never retrieved\n", - "future: exception=NotRunningException()>\n", - "Traceback (most recent call last):\n", - " File \"/usr/lib/python3.10/site-packages/zeroconf/_services/browser.py\", line 445, in _async_start_query_sender\n", - " await self.zc.async_wait_for_start()\n", - " File \"/usr/lib/python3.10/site-packages/zeroconf/_core.py\", line 507, in async_wait_for_start\n", - " raise NotRunningException\n", - "zeroconf._exceptions.NotRunningException\n" - ] - } - ], - "source": [ - "keithley.write(\"format.data = format.DREAL\\nformat.byteorder = format.LITTLEENDIAN\")\n" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "4ffc1758-a27e-4d4f-87ad-d969da05dbad", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.000000e+00, 5.000000e-02, 1.000000e-01, 1.500000e-01, 2.000000e-01, 2.500000e-01, 3.000000e-01, 3.500000e-01, 4.000000e-01, 4.500000e-01, 5.000000e-01, 5.500000e-01, 6.000000e-01, 6.500000e-01, 7.000000e-01, 7.500000e-01, 8.000000e-01, 8.500000e-01, 9.000000e-01, 9.500000e-01, 1.000000e+00, 1.050000e+00, 1.100000e+00, 1.150000e+00, 1.200000e+00, 1.250000e+00, 1.300000e+00, 1.350000e+00, 1.400000e+00, 1.450000e+00, 1.500000e+00, 1.550000e+00, 1.600000e+00, 1.690891e+00, 1.710923e+00, 1.750000e+00, 1.800000e+00, 1.850000e+00, 1.900000e+00, 1.950000e+00, 2.000000e+00, 2.050000e+00, 2.100000e+00, 2.150000e+00, 2.200000e+00, 2.250000e+00, 2.300000e+00, 2.350000e+00, 2.420332e+00, 2.450000e+00, 2.500000e+00, 2.550000e+00, 2.600000e+00, 2.650000e+00, 2.700000e+00, 2.750000e+00, 2.820553e+00, 2.890843e+00, 2.910875e+00, 2.950000e+00, 3.000000e+00, 3.050000e+00, 3.100000e+00, 3.150000e+00, 3.200000e+00, 3.250000e+00, 3.300000e+00, 3.350000e+00, 3.400000e+00, 3.450000e+00, 3.500000e+00, 3.550000e+00, 3.600000e+00, 3.650000e+00, 3.700000e+00, 3.750000e+00, 3.800000e+00, 3.850000e+00, 3.900000e+00, 3.950000e+00, 4.000000e+00, 4.050000e+00, 4.100000e+00, 4.150000e+00, 4.200000e+00, 4.250000e+00, 4.300000e+00, 4.350000e+00, 4.400000e+00, 4.450000e+00, 4.500000e+00, 4.550000e+00, 4.600000e+00, 4.650000e+00, 4.700000e+00, 4.750000e+00, 4.800000e+00, 4.850000e+00, 4.900000e+00, 4.950000e+00\n", - "\n", - "[100.0]\n" - ] - } - ], - "source": [ - "print(keithley.query(f\"print(smua.nvbuffer1.n)\"))\n", - "keithley.write(\"format.data = format.ASCII\")\n", - "buffer = keithley.query_ascii_values(f\"printbuffer(1, smua.nvbuffer1.n, smua.nvbuffer1.timestamps)\") #, datatype='d', container=np.array)\n", - "print(buffer)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "b7c57345-13ff-4f52-a4b0-58561dcab0a8", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "readings: [ 4.974127e-10 -1.418591e-12 -1.573563e-12 -1.728535e-12 -1.704693e-12\n", - " -1.847744e-12 -1.931190e-12 -1.788139e-12 -1.871586e-12 -2.169609e-12\n", - " -2.074242e-12 -1.895428e-12 -1.895428e-12 -1.776218e-12 -1.990795e-12\n", - " -1.788139e-12 -1.811981e-12 -1.657009e-12 -1.573563e-12 -2.396107e-12\n", - " -2.515316e-12 -2.765656e-12 -2.825260e-12 -3.147125e-12 -2.300739e-12\n", - " -2.825260e-12 -3.278255e-12 -5.257130e-12 -6.818771e-12 -8.916855e-12\n", - " -7.712841e-12 6.437302e-12 -1.142025e-11 -1.206398e-11 -4.649043e-10\n", - " -3.427613e-09 -2.460408e-09 -2.340376e-09 -1.306653e-10 1.496077e-11\n", - " 2.933741e-11 1.953280e-09 8.579970e-10 9.226799e-12 -1.095533e-11\n", - " -2.508163e-11 -2.776039e-09 -8.686423e-09 4.935264e-12 1.246929e-11\n", - " 3.225744e-09 2.814472e-09 1.877034e-09 2.229273e-09 1.713574e-09\n", - " 8.355618e-10 -4.332781e-10 5.896091e-11 5.762577e-11 8.129537e-09\n", - " 4.044378e-09 1.771629e-09 7.849216e-10 4.098892e-10 3.390551e-10\n", - " 2.956390e-10 3.033876e-10 1.716256e-10 1.463890e-11 -5.078316e-12\n", - " -6.949902e-12 -8.106232e-12 -6.473065e-12 -4.506111e-12 4.919767e-11\n", - " 3.052297e-08 1.161162e-08 -9.892106e-09 -3.613818e-09 -5.004287e-09\n", - " -2.015829e-11 -4.183054e-11 -1.810908e-10 -2.042532e-10 -3.516316e-10\n", - " 5.099773e-11 1.921976e-08 -1.256589e-08 -4.242897e-10 -1.358986e-12\n", - " -3.445148e-12 -3.838539e-12 -4.184246e-12 -7.402897e-12 -2.840877e-10\n", - " -2.872229e-10 -2.730966e-10 -1.134396e-10 -4.376173e-11 -3.576279e-14], \n", - "timestamps: [0. 0.05 0.1 0.15 0.2 0.25 0.3 0.35\n", - " 0.4 0.45 0.5 0.55 0.6 0.65 0.7 0.75\n", - " 0.8 0.85 0.9 0.95 1. 1.05 1.1 1.15\n", - " 1.2 1.25 1.3 1.35 1.4 1.45 1.5 1.55\n", - " 1.6 1.690891 1.710923 1.75 1.8 1.85 1.9 1.95\n", - " 2. 2.05 2.1 2.15 2.2 2.25 2.3 2.35\n", - " 2.420332 2.45 2.5 2.55 2.6 2.65 2.7 2.75\n", - " 2.820553 2.890843 2.910875 2.95 3. 3.05 3.1 3.15\n", - " 3.2 3.25 3.3 3.35 3.4 3.45 3.5 3.55\n", - " 3.6 3.65 3.7 3.75 3.8 3.85 3.9 3.95\n", - " 4. 4.05 4.1 4.15 4.2 4.25 4.3 4.35\n", - " 4.4 4.45 4.5 4.55 4.6 4.65 4.7 4.75\n", - " 4.8 4.85 4.9 4.95 ]\n", - "readings: [-1.556139e-01 -1.663574e-01 -1.621783e-01 -1.685456e-01 -1.634915e-01\n", - " -1.711711e-01 -1.661170e-01 -1.711710e-01 -1.665548e-01 -1.742343e-01\n", - " -1.669925e-01 -1.724839e-01 -1.674302e-01 -1.733590e-01 -1.669922e-01\n", - " -1.729215e-01 -1.687431e-01 -1.772979e-01 -1.770578e-01 -1.829874e-01\n", - " -1.674301e-01 -1.606676e-01 -1.437982e-01 -1.322218e-01 -1.140394e-01\n", - " -1.151545e-01 -9.478331e-02 -6.351433e-02 -2.557993e-04 6.995752e-02\n", - " 1.734703e-01 1.487249e-01 1.095815e-01 1.079751e+01 1.263197e+01\n", - " 1.362582e+01 1.300827e+01 1.259473e+01 1.025542e+01 3.997076e+00\n", - " -9.946833e+00 -1.482129e+01 -1.399363e+01 -1.326294e+01 -1.132879e+01\n", - " -5.142632e+00 1.148345e+01 1.378293e+01 -3.400979e-01 -9.995720e-01\n", - " -1.749203e+00 -1.385303e+00 -1.056456e+00 -1.082226e+00 -8.675261e-01\n", - " -7.083023e-01 8.988955e-02 4.139638e+00 -6.590693e+00 -1.697538e+01\n", - " -1.556470e+01 -1.461764e+01 -1.392116e+01 -1.365366e+01 -1.351573e+01\n", - " -1.343544e+01 -1.343307e+01 -1.329787e+01 -1.297363e+01 -1.218130e+01\n", - " -1.076091e+01 -8.683739e+00 -6.708155e+00 -5.400786e+00 -7.245429e+00\n", - " -1.917886e+01 -1.436163e+01 1.541487e+01 1.495236e+01 1.484173e+01\n", - " 1.273334e+01 1.251023e+01 1.266481e+01 1.274583e+01 1.290255e+01\n", - " 1.080429e+01 -1.518563e+01 1.614336e+01 1.317309e+01 1.189001e+01\n", - " 1.109593e+01 1.094126e+01 1.113482e+01 1.139770e+01 1.268495e+01\n", - " 1.251639e+01 1.248731e+01 1.230242e+01 1.220341e+01 1.181950e+01], \n", - "timestamps: [0. 0.05 0.1 0.15 0.2 0.25 0.3 0.35\n", - " 0.4 0.45 0.5 0.55 0.6 0.65 0.7 0.75\n", - " 0.8 0.85 0.9 0.95 1. 1.05 1.1 1.15\n", - " 1.2 1.25 1.3 1.35 1.4 1.45 1.5 1.55\n", - " 1.6 1.690891 1.710923 1.75 1.8 1.85 1.9 1.95\n", - " 2. 2.05 2.1 2.15 2.2 2.25 2.3 2.35\n", - " 2.420332 2.45 2.5 2.55 2.6 2.65 2.7 2.75\n", - " 2.820553 2.890843 2.910875 2.95 3. 3.05 3.1 3.15\n", - " 3.2 3.25 3.3 3.35 3.4 3.45 3.5 3.55\n", - " 3.6 3.65 3.7 3.75 3.8 3.85 3.9 3.95\n", - " 4. 4.05 4.1 4.15 4.2 4.25 4.3 4.35\n", - " 4.4 4.45 4.5 4.55 4.6 4.65 4.7 4.75\n", - " 4.8 4.85 4.9 4.95 ]\n" - ] - } - ], - "source": [ - "buffer1 = measure.collect_buffer(keithley, buffer_nr=1)\n", - "buffer2 = measure.collect_buffer(keithley, buffer_nr=2)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "3390e755-9e37-4b12-8499-9afb577d6505", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'buffer1' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[2], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[43mbuffer1\u001b[49m)\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpandas\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mpd\u001b[39;00m\n\u001b[1;32m 3\u001b[0m df \u001b[38;5;241m=\u001b[39m pd\u001b[38;5;241m.\u001b[39mDataFrame(buffer1)\n", - "\u001b[0;31mNameError\u001b[0m: name 'buffer1' is not defined" - ] - } - ], - "source": [ - "print(buffer1)\n", - "import pandas as pd\n", - "df = pd.DataFrame(buffer1)\n", - "df.colums = [\"Time [s]\", \"Voltage [V]\"]\n", - "df.save_csv(\"test.csv\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2beb660c-62b7-4d66-8162-601dfabd53f1", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "\n", - "\n", - "plt.plot(buffer1)\n", - "plt.plot(buffer2)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "a96e2c53-67fa-49ac-87b4-22c34190a2b7", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "image/png": "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+Pg4zjvvPDz00ENJ327kObvzzjvxy1/+Ek888QReeukljI2N4eqrr0YoFCrUw0gr3WOcmJjAG2+8ga997Wt444038NRTT+Hdd9/Ftddem3DfW2+9Vfe8/vCHPyzE8g3J9DwCmb8vrfw8Znp82sfV3d2Nn/70p1AUBR/+8Id197Pyc2jkGmGJn0dBCS666CJx22236W4788wzxT333GPSivKnt7dXABDPP/+8etvNN98sPvCBD5i3qGn6xje+Ic4777ykbwuHw6KlpUV8+9vfVm+bmpoStbW14pFHHinQCvPvi1/8oli0aJEIh8NCCHs/hwDEL3/5S/V1I8/Z0NCQcLvd4oknnlDvc+LECeFwOMTvfve7gq3dqPjHmMxrr70mAIijR4+qt1122WXii1/84swuLk+SPcZM35d2eh6NPIcf+MAHxBVXXKG7zU7PoRCJ1wir/DwysxLH7/dj586dWL9+ve729evX4+WXXzZpVfkzPDwMAKivr9fd/txzz6GpqQlLly7Frbfeit7eXjOWl7MDBw6gra0NCxYswMaNG3H48GEAQGdnJ3p6enTPp9frxWWXXWbb59Pv9+PnP/85PvnJT+oO77T7cygZec527tyJQCCgu09bWxuWLVtm2+d1eHgYiqJg1qxZutv//d//HY2NjTjnnHPw5S9/2VYZQSD992UxPY+nTp3C008/jU996lMJb7PTcxh/jbDKz6PtDzLMt/7+foRCITQ3N+tub25uRk9Pj0mryg8hBO666y685z3vwbJly9TbN2zYgOuvvx4dHR3o7OzE1772NVxxxRXYuXOnLaYxrl69Gj/72c+wdOlSnDp1Ct/85jexdu1a7N27V33Okj2fR48eNWO50/arX/0KQ0NDuOWWW9Tb7P4cahl5znp6euDxeFBXV5dwHzv+nE5NTeGee+7BTTfdpDsg7qMf/SgWLFiAlpYWvPXWW9i0aRPefPNNdRvQ6jJ9XxbT8/j444+juroa1113ne52Oz2Hya4RVvl5ZLCSgvYvViDyJMbfZjd33HEH/vznP+Oll17S3X7DDTeo/1+2bBlWrVqFjo4OPP300wk/eFa0YcMG9f/Lly/HmjVrsGjRIjz++ONqMV8xPZ8/+clPsGHDBrS1tam32f05TCaX58yOz2sgEMDGjRsRDofx8MMP69526623qv9ftmwZlixZglWrVuGNN97AihUrCr3UrOX6fWnH5/GnP/0pPvrRj6KsrEx3u52ew1TXCMD8n0duA8VpbGyE0+lMiAZ7e3sTIks7+fznP49f//rX2LZtG+bOnZv2vq2trejo6MCBAwcKtLr8qqysxPLly3HgwAG1K6hYns+jR4/i2Wefxac//em097Pzc2jkOWtpaYHf78fg4GDK+9hBIBDARz7yEXR2dmLr1q26rEoyK1asgNvttuXzCiR+XxbL8/jiiy9i//79GX8uAes+h6muEVb5eWSwEsfj8WDlypUJKbqtW7di7dq1Jq0qd0II3HHHHXjqqafwxz/+EQsWLMj4PgMDAzh27BhaW1sLsML88/l8ePvtt9Ha2qqmX7XPp9/vx/PPP2/L5/PRRx9FU1MT3v/+96e9n52fQyPP2cqVK+F2u3X36e7uxltvvWWb51UGKgcOHMCzzz6LhoaGjO+zd+9eBAIBWz6vQOL3ZTE8j0Ak27ly5Uqcd955Ge9rtecw0zXCMj+PeSnTLTJPPPGEcLvd4ic/+YnYt2+fuPPOO0VlZaU4cuSI2UvL2mc/+1lRW1srnnvuOdHd3a2+TExMCCGEGB0dFXfffbd4+eWXRWdnp9i2bZtYs2aNmDNnjhgZGTF59cbcfffd4rnnnhOHDx8W27dvF1dffbWorq5Wn69vf/vbora2Vjz11FNiz5494sYbbxStra22eXxSKBQS8+bNE1/5yld0t9vxORwdHRW7du0Su3btEgDEgw8+KHbt2qV2whh5zm677TYxd+5c8eyzz4o33nhDXHHFFeK8884TwWDQrIelk+4xBgIBce2114q5c+eK3bt36342fT6fEEKIgwcPivvuu0/s2LFDdHZ2iqefflqceeaZ4oILLrDFYzT6fWnl5zHT96kQQgwPD4uKigrxgx/8IOH97fAcZrpGCGGNn0cGKyn867/+q+jo6BAej0esWLFC1+prJwCSvjz66KNCCCEmJibE+vXrxezZs4Xb7Rbz5s0TN998s+jq6jJ34Vm44YYbRGtrq3C73aKtrU1cd911Yu/everbw+Gw+MY3viFaWlqE1+sV733ve8WePXtMXHFufv/73wsAYv/+/brb7fgcbtu2Len35c033yyEMPacTU5OijvuuEPU19eL8vJycfXVV1vqMad7jJ2dnSl/Nrdt2yaEEKKrq0u8973vFfX19cLj8YhFixaJL3zhC2JgYMDcB6aR7jEa/b608vOY6ftUCCF++MMfivLycjE0NJTw/nZ4DjNdI4Swxs+jEl0sERERkSWxZoWIiIgsjcEKERERWRqDFSIiIrI0BitERERkaQxWiIiIyNIYrBAREZGlMVghIiIiS2OwQkRERJbGYIWIiIgsjcEKERERWRqDFSIiIrI0BitERERkaf8fM7Blwx2VNcAAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "38" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import clear_output\n", - "\n", - "smua_settings = \"\"\"\n", - "display.clear() \n", - "display.settext('starting')\n", - "smua.reset()\n", - "smua.measure.autorangev = smua.AUTORANGE_ON\n", - "smua.source.output = smua.OUTPUT_OFF\n", - "-- max 20 V expected\n", - "smua.measure.rangev = 20\n", - "\"\"\"\n", - "measure.run_lua(keithley, \"smua_reset.lua\")\n", - "keithley.write(\"smua.source.output = smua.OUTPUT_ON\")\n", - "data = []\n", - "for i in range(200):\n", - " data.append(tuple(float(v) for v in keithley.query(\"print(smua.measure.v())\").strip('\\n').split('\\t')))\n", - " # print(i, data[-1])\n", - " clear_output(wait=True)\n", - " plt.plot(data)\n", - " plt.show()\n", - " sleep(0.05)\n", - "\n", - "keithley.write(\"smua.source.output = smua.OUTPUT_OFF\")" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "4e78e53e-a34d-4425-906e-0123a502d1f5", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "ename": "VisaIOError", - "evalue": "VI_ERROR_TMO (-1073807339): Timeout expired before operation completed.", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mVisaIOError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[13], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mkeithley\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread_raw\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m/usr/lib/python3.10/site-packages/pyvisa/resources/messagebased.py:405\u001b[0m, in \u001b[0;36mMessageBasedResource.read_raw\u001b[0;34m(self, size)\u001b[0m\n\u001b[1;32m 388\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mread_raw\u001b[39m(\u001b[38;5;28mself\u001b[39m, size: Optional[\u001b[38;5;28mint\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28mbytes\u001b[39m:\n\u001b[1;32m 389\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Read the unmodified string sent from the instrument to the computer.\u001b[39;00m\n\u001b[1;32m 390\u001b[0m \n\u001b[1;32m 391\u001b[0m \u001b[38;5;124;03m In contrast to read(), no termination characters are stripped.\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 403\u001b[0m \n\u001b[1;32m 404\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 405\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mbytes\u001b[39m(\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_read_raw\u001b[49m\u001b[43m(\u001b[49m\u001b[43msize\u001b[49m\u001b[43m)\u001b[49m)\n", - "File \u001b[0;32m/usr/lib/python3.10/site-packages/pyvisa/resources/messagebased.py:442\u001b[0m, in \u001b[0;36mMessageBasedResource._read_raw\u001b[0;34m(self, size)\u001b[0m\n\u001b[1;32m 435\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m status \u001b[38;5;241m==\u001b[39m loop_status:\n\u001b[1;32m 436\u001b[0m logger\u001b[38;5;241m.\u001b[39mdebug(\n\u001b[1;32m 437\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m - reading \u001b[39m\u001b[38;5;132;01m%d\u001b[39;00m\u001b[38;5;124m bytes (last status \u001b[39m\u001b[38;5;132;01m%r\u001b[39;00m\u001b[38;5;124m)\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 438\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_resource_name,\n\u001b[1;32m 439\u001b[0m size,\n\u001b[1;32m 440\u001b[0m status,\n\u001b[1;32m 441\u001b[0m )\n\u001b[0;32m--> 442\u001b[0m chunk, status \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvisalib\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msession\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msize\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 443\u001b[0m ret\u001b[38;5;241m.\u001b[39mextend(chunk)\n\u001b[1;32m 444\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m errors\u001b[38;5;241m.\u001b[39mVisaIOError \u001b[38;5;28;01mas\u001b[39;00m e:\n", - "File \u001b[0;32m/usr/lib/python3.10/site-packages/pyvisa_py/highlevel.py:519\u001b[0m, in \u001b[0;36mPyVisaLibrary.read\u001b[0;34m(self, session, count)\u001b[0m\n\u001b[1;32m 513\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m:\n\u001b[1;32m 514\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m (\n\u001b[1;32m 515\u001b[0m \u001b[38;5;124mb\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 516\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandle_return_value(session, StatusCode\u001b[38;5;241m.\u001b[39merror_invalid_object),\n\u001b[1;32m 517\u001b[0m )\n\u001b[0;32m--> 519\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m data, \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mhandle_return_value\u001b[49m\u001b[43m(\u001b[49m\u001b[43msession\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstatus_code\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m/usr/lib/python3.10/site-packages/pyvisa/highlevel.py:251\u001b[0m, in \u001b[0;36mVisaLibraryBase.handle_return_value\u001b[0;34m(self, session, status_code)\u001b[0m\n\u001b[1;32m 248\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_last_status_in_session[session] \u001b[38;5;241m=\u001b[39m rv\n\u001b[1;32m 250\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m rv \u001b[38;5;241m<\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[0;32m--> 251\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m errors\u001b[38;5;241m.\u001b[39mVisaIOError(rv)\n\u001b[1;32m 253\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m rv \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39missue_warning_on:\n\u001b[1;32m 254\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m session \u001b[38;5;129;01mand\u001b[39;00m rv \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_ignore_warning_in_session[session]:\n", - "\u001b[0;31mVisaIOError\u001b[0m: VI_ERROR_TMO (-1073807339): Timeout expired before operation completed." - ] - } - ], - "source": [ - "keithley.read_raw()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d597ff99-d56d-45c4-8e1c-3ee1eebfb565", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "measure.run_lua(keithley, \"buffer_reset.lua\")\n", - "measure.run_lua(keithley, \"smua_reset.lua\")\n", - "keithley.write(\"smua.measure.count = 100\")\n", - "keithley.write(\"smua.measure.interval = 0.05\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e7adaa9-e81d-498b-9e01-b838643a1050", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - " keithley.write(\"smua.measure.iv(smua.nvbuffer1, smua.nvbuffer2)\")\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "aafa7d7e-dedd-4546-bf80-b247eb947402", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "keithley.write(\"format.data = format.DREAL\\nformat.byteorder = format.LITTLEENDIAN\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "40dbecd1-7405-4a6c-81d9-0d3f407c0efc", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "keithley.query_binary_values(\"printbuffer(1, smua.nvbuffer1.n, smua.nvbuffer1)\", container=)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3979a9de-f4b9-448e-a2f3-b36cbea9658c", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "60431b81-a860-4964-8f60-0aca8fdde36a", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..91161d1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = ["setuptools"] + +[project] +name = "k_teng" +version = "1.0.0" +description = "Interactive utility for I-V measurements with a Keitley 2600 SMU" +requires-python = ">=3.10" +readme = "readme.md" +license = {file = "LICENSE"} +authors = [ + { name = "Matthias Quintern", email = "matthias@quintern.xyz" } +] +classifiers = [ + "Operating System :: POSIX :: Linux", + "Environment :: Console", + "Programming Language :: Python :: 3", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", +] +dependencies = [ + "matplotlib>=3.6", + "numpy", + "pyvisa", + "pyvisa-py" +] + +[project.urls] +repository = "https://git.quintern.xyz/MatthiasQuintern/k-teng" + + +[tool.setuptools.packages.find] +where = ["."] + +[project.scripts] +k-teng = "k_teng.k_teng_interactive:main" diff --git a/readme.md b/readme.md index bb53760..b9e86ca 100644 --- a/readme.md +++ b/readme.md @@ -1,23 +1,39 @@ -# K-TENG -Helper scripts and shell for measuring **T**ribo**e**lectric **N**ano**g**enerator-based sensor output with a Keithley 2611B SMU using pyvisa +# m-TENG +Helper scripts and shell for measuring **T**ribo**e**lectric **N**ano**g**enerator-based sensor output with a Keithley 2611B SMU or an Arduino ## Features -### Useful functions for scripts -- Measure Voltage and/or Current -- Transfer buffer from device to host -- Save/load as csv -- Run lua script on device -- Auto-filenames + ### Interactive (shell) mode - Live view - Press button to stop - Save and load settings (default interval, data directory...) - Easily run arbitrary command on device + +### Useful functions for scripts +- Measure voltage and/or current +- Transfer buffer from measurement device to host +- Save/load as csv +- Run lua script on Keithley SMU +- Auto-filenames + +## Available backends +### keithley + Use a Keithley 2611B Source-Measure-Unit via *pyvisa*. This backend allows measuring both voltage and current simultaneously. + +### arduino + Use a Bluetooth capable Arduino with [https://git.quintern.xyz/MatthiasQuintern/teng-arduino](this software on the arduino). + This backend only allows measuring voltage using an Arduinos analog input pin (0-5 V, 10 bit resolution). + +### testing + Use the shell without measuring TENG output. When starting a measurement, sample data will be generated. + + ## Shell mode -Start with: +It is recommended to run the shell with ipython: ```shell -ipython -i k_teng_interactive.py +ipython -i k_teng_interactive.py -- -*X* ``` +Substitute *X* for `-k` for keithley backend, `-a` for arduino backend or `-t` for testing backend. Use `help()` to get a list of available commands