Ustatnia modyfikacja 13 listopada 2023 przez Olek

W ramach mojej pracy w UMG stworzyłem program w Python który automatycznie pobiera bieżące wartości pomiarów z urządzenie i odpowiednio je rejestruje. Wynik na bieżąco przedstawia w postaci gotowych wykresów-charakterystyk. Dla wygody obsługi program posiada przyjazny interfejs graficzny napisany z wykorzystaniem popularnej biblioteki to Python-a tkinter (tk)1. Komunikację z urządzenie zapewnia biblioteka VISA2 (pyvisa3). Urządzenie musi być odpowiednio skonfigurowane i podłączone do sieci Ethernet. Na komputerze powinny być zainstalowane oprogramowanie do komunikacji z urządzeniami pomiarowymi NI4 VISA5. W poniższym przykładzie program posłużył do pomiarów prądów zerowych tranzystora bipolarnego w różnych temperaturach otoczenia Ta. Uzyskane wyniki na bieżąco zostały przeliczane i porównywane z wartościami granicznymi aby nie uszkodzić mierzonego tranzystora.

  1. Wskazanie urządzenia, z którym się chcemy podłączyć, według standardów VISA
  2. Parametry programu
    • Tytuł pomiaru/wykresu/danych
    • Ta – temperatura otoczenia. Na jej podstawie tworzona jest etykieta serii danych jak również obliczana temperatura Tj
    • Tj_max – maksymalna temperatura złącza. Służy do ostrzeżenia przed przekroczeniem tej wartości. Po jej przekroczeniu wyniki pomiarów (pkt 3) są przedstawiane na czerwono
  3. Tutaj przedstawiany jest aktualny pomiar z urządzenia jak również obliczane wartości P i Tj. Przycisk ZAPISZ służy do zapisania bieżącego pomiaru.
  4. Wykres tworzony na podstawie zapisanych pomiarówa.
  5. Funkcje służące do konfiguracji, przeglądania i zapisu wykresu
  6. Funkcje sterujące programem i zapisanymi danymi.
  1. https://docs.python.org/3/library/tkinter.html ↩︎
  2. Virtual Instrument Software Architecture ↩︎
  3. https://pyvisa.readthedocs.io/en/latest/ ↩︎
  4. National Instruments ↩︎
  5. https://www.ni.com/en/support/downloads/drivers/download.ni-visa.html ↩︎

Poniżej cały omawiany skrypt.

"""
    Komunakacja z multimetrem rigol
    z interfejsem graficznym - it
"""
from __future__ import absolute_import, division, print_function

import serial
import time
import tkinter as tk
from builtins import *  # @UnusedWildImport
from ctypes import POINTER, c_double, c_ulong, c_ushort, cast
from tkinter import filedialog as fd
from tkinter import messagebox, ttk

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
import os.path
import re
import pyvisa
import sys
import os
from matplotlib.ticker import EngFormatter
from datetime import datetime
from pathlib import Path

import yaml #pracuje już na bieżącej pyyaml czyli 6; poprezednio  pip install pyyaml==5.4.1



c_port      = 'port'
c_title     = 'title'
c_Ta        = 'Ta'
c_Tj_max    = 'Tj_max' 
c_Rth       = 'Rth'


column_time = 'Czas'
column_U = 'U'
column_I = 'I'


CFG = yaml.load(
"""
port:       'TCPIP0::192.168.0.154::inst0::INSTR'
title:      'Prąd zerowy BJT C-CiC (GA05JT03)'
Ta:         25
Tj_max:     200
Rth:        10 
"""
, Loader=yaml.Loader)

#print(yaml.dump(CFG))
#sys.exit(0)

WERSJA = "0.0.1"    


def f(v):
  fm = EngFormatter(sep="", places=2)
  return fm.format_eng(v)


class Rigol(tk.Frame, object):



    def __init__(self, master=None):
        super(Rigol, self).__init__(master)

        example_dir = os.path.dirname(os.path.realpath(__file__))
#        icon_path = os.path.join(example_dir, 'MCC.ico')
#        master.iconbitmap(icon_path)
        master.wm_title(type(self).__name__)
        master.minsize(width=400, height=400)
        master.grid_columnconfigure(0, weight=1)
        master.grid_rowconfigure(0, weight=1)

        self.grid(sticky=tk.NSEW)

        self.tab_info_text = None
        self.tab_plt = None
        self.fig_plt = None
        self.canvas = None
        self.rigs = []

        dir = os.path.dirname(os.path.realpath(__file__)) 
        fname = dir+'\\'+type(self).__name__+'.yaml'
        if os.path.isfile(fname) :
            with open(fname, 'r') as file:
                #CFG = yaml.load(file)
                CFG = yaml.load(file, Loader=yaml.Loader)
                #print(yaml.dump(CFG))
                #pass
            
        self.create_widgets()
        self.set_data()
        self.run = False
        #self.after(int(CFG[c_timer])*1000, self.idle_time)
        self.after(100, self.idle_time)
        self.after(1000, self.idle_1000ms)
        self.ii = 0
        #self.df = pd.DataFrame(columns=[column_U,column_I])
        #self.df.index.name = column_time

        #self.df = pd.DataFrame(columns=[column_I])
        self.df = pd.DataFrame()
        self.df.index.name = column_U

        #print(self.df)


    def create_widgets(self):

        device_frame = tk.LabelFrame(self, text = "Urządzenie")
        device_frame.pack(fill=tk.X, anchor=tk.CENTER, padx=3, pady=3)

        l = tk.Label(device_frame)
        l["text"] = "Rigol:"
        l.grid(row=0, column=0, sticky=tk.W)
        self.port = tk.Entry(device_frame, width=40)
        self.port.grid(row=0, column=1, sticky=tk.W, padx=3, pady=3)

        l = tk.Label(device_frame)
        l["text"] = "  Wersja programu: "+WERSJA
        l.grid(row=0, column=3, sticky=tk.W)


        fr = tk.LabelFrame(self, text = "Parametry", height = 20)
        fr.pack(fill=tk.X, anchor=tk.NW, padx=3, pady=3)
        c = 0

        l = tk.Label(fr)
        l["text"] = "Tytuł:"
        l.grid(row=0, column=c, sticky=tk.W)
        c=c+1
        self.title = tk.Entry(fr, width=60)
        self.title.grid(row=0, column=c, sticky=tk.W, padx=3, pady=3)
        c=c+1

        l = tk.Label(fr)
        l["text"] = "Ta:"
        l.grid(row=0, column=c, sticky=tk.W)
        c+=1
        self.Ta = tk.Entry(fr, width=10)
        self.Ta.grid(row=0, column=c, sticky=tk.W, padx=3, pady=3)
        c+=1
        l = tk.Label(fr)
        l["text"] = "Tj_max:"
        l.grid(row=0, column=c, sticky=tk.W)
        c+=1
        self.Tj_max = tk.Entry(fr, width=10)
        self.Tj_max.grid(row=0, column=c, sticky=tk.W, padx=3, pady=3)
        c=c+1
        l = tk.Label(fr)
        l["text"] = "Rth:"
        l.grid(row=0, column=c, sticky=tk.W)
        c+=1
        self.Rth = tk.Entry(fr, width=10)
        self.Rth.grid(row=0, column=c, sticky=tk.W, padx=3, pady=3)
        c=0
        fr = tk.LabelFrame(self, text = "Pomiar", height = 20)
        fr.pack(fill=tk.X, anchor=tk.NW, padx=3, pady=3)
        b = tk.Button(fr)
        b["text"] = "[---------------Z A P I S Z---------------]"
        b["command"] = self.send_cmd
        b.grid(row=1, column=c, padx=3, pady=3,  sticky=tk.W)
        c+=1
        self.pomiar = tk.Label(fr)
        self.pomiar["text"] = "pomiar"

        u=500
        i=0.00006
        p = u*i
        Ta = 25
        Rth = 10
        Tj = Ta + p * Rth

        self.pomiar["text"] = f'U={f(u)}V  I={f(i)}A  P={f(p)}W  Tj={f(Tj)}C'
        #self.pomiar["text"] = f'xyz'        

        self.pomiar.grid(row=1, column=c, sticky=tk.E)
        c+=1

        self.results_group = tk.LabelFrame(self, text="Wyniki", padx=3, pady=3)
        self.results_group.pack(expand=1, fill=tk.BOTH, anchor=tk.NW, padx=3, pady=3)
        self.results_group.grid_columnconfigure(1, weight=1)

        tabs = ttk.Notebook(self.results_group)

        self.tab_info_text = ttk.Frame(tabs, height = 200)
        self.tab_info_text.pack(fill=tk.BOTH)
        self.tab_plt = ttk.Frame(tabs, height = 200)
        tabs.add(self.tab_info_text, text='Log (teksty info)')
        tabs.add(self.tab_plt, text='Wykres')

#        t2 = ttk.Label(tab_plt, text= 'opis 2')
#        t2.grid(column=0, row=0)

        self.info_text = tk.Text(self.tab_info_text, height = 10)
        self.info_text.pack(expand=1, side=tk.LEFT, fill=tk.BOTH)

    # create a Scrollbar and associate it with txt
        sb = tk.Scrollbar(self.tab_info_text, command=self.info_text.yview)
        sb.pack(side=tk.RIGHT, fill=tk.Y)

        tabs.pack(expand=1, fill=tk.BOTH)
        button_frame = tk.Frame(self, height = 50)
        button_frame.pack(expand = 0, fill=tk.NONE, side=tk.BOTTOM, anchor=tk.SE)
        c = 0
        self.but_connect= tk.Button(button_frame)
        self.but_connect["text"] = "Podłącz"
        self.but_connect["command"] = self.connect
        self.but_connect.grid(row=0, column=c, padx=3, pady=3)
        c += 1
        b = tk.Button(button_frame)
        b["text"] = "Zapisz parametry"
        b["command"] = self.save_CFG
        b.grid(row=0, column=c, padx=3, pady=3)
        c += 1
        b = tk.Button(button_frame)
        b["text"] = "Zapis dane"
        b["command"] = self.save_df
        b.grid(row=0, column=c, padx=3, pady=3)
        c += 1
        b = tk.Button(button_frame)
        b["text"] = "Odczyt dane"
        b["command"] = self.load_df
        b.grid(row=0, column=c, padx=3, pady=3)
        c += 1
        self.quit_button = tk.Button(button_frame)
        self.quit_button["text"] = "Wyjście"
        self.quit_button["command"] = self.master.destroy
        self.quit_button.grid(row=0, column=c, padx=3, pady=3)
        c += 1


    def get_data(self):
        CFG[c_port]     = self.port.get()
        CFG[c_title]     = self.title.get()
        try:
            ss = self.Ta.get()
            CFG[c_Ta]        = int(ss)
        except:
            pass
        try:
            CFG[c_Tj_max]    = int(self.Tj_max.get())
        except:
            pass
        try:
            CFG[c_Rth]       = int(self.Rth.get())
        except:
            pass


    def set_data(self):
        try:
            self.port.insert(0, str(CFG[c_port]))
            self.title.insert(0, str(CFG[c_title]))
            self.Ta.insert(0, str(CFG[c_Ta]))
            self.Tj_max.insert(0, str(CFG[c_Tj_max]))
            self.Rth.insert(0, str(CFG[c_Rth]))
        except:
            pass

    def connect(self):
        self.get_data()
        if len(self.rigs)>0:
            for rig in self.rigs:
                self.print_info('Rozłączyłem się z portem ' + CFG[c_port] + '\n')
                rig = rm.open_resource(port)  
                self.print_info('Rozłączyłem się z portem ' + CFG[c_port] + '\n')
            self.but_connect["text"] = "Podłącz"
            self.ser = None
        else:
            port = CFG[c_port]
            try:
                rm = pyvisa.ResourceManager()
                self.print_info('Zasoby"\n')
                for l in rm.list_resources():
                    self.print_info(f"{l}\n")
                port = CFG[c_port]
                rig = rm.open_resource(port)  
                rig.write(":FUNCtion:CURRent:DC")   # ustawienie na IDC
                #pomiar napięcie, jeśli jest obsługiwany drugi kanał to na drugim kanale
                rig.write(":FUNCtion2:VOLTage:DC")   # ustawienie 2 na VDC
                self.print_info(f'Połączyłem się z RIGOLEM {port} \n')
                self.rigs.append(rig)
                
                self.but_connect["text"] = "Rozłącz"
            except:
                self.print_info(f'Nie mogłem połączyć się z portem {port}\n')
                self.rig = []


    def print_info(self, info):
        self.info_text.insert(tk.END, info)
        self.info_text.update()
        self.info_text.see(tk.END)
        self.info_text.edit_modified(0)

    def graf(self):
        if  self.df.empty:
            self.print_info("Brak danych\n".format())
            return
        if self.fig_plt is None:
            self.fig_plt = Figure(figsize=(5, 2), dpi=100)
            self.ax = self.fig_plt.add_subplot(111).plot(self.df,'-')

            #self.fig_plt, self.ax = plt.subplots(figsize=(5, 2))

            self.canvas = FigureCanvasTkAgg(self.fig_plt, master=self.tab_plt)  # A tk.DrawingArea.
            self.canvas.draw()
            self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)

            toolbar = NavigationToolbar2Tk(self.canvas, self.tab_plt)
            toolbar.update()
            self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)

        self.fig_plt.clf()
        self.fig_plt.add_subplot(111).plot(self.df,'-')

        #cs = self.df.columns.to_list()
        #self.fig_plt.legend(cs)


        self.fig_plt.suptitle(CFG[c_title])
        now = datetime.now()
        self.fig_plt.text(0, 0, now, fontsize=8, ha='left',  va='bottom', wrap=False)

        ef0 = EngFormatter(sep="", places=0)
        ef2 = EngFormatter(sep="", places=2)

        #ax_list = self.fig_plt.axes
        #print('ax_list=',ax_list)
        #for ax in ax_list:
        #    print('ax=',ax)

        ax = self.fig_plt.axes[0]
        ax.yaxis.set_major_formatter(ef0)
        ax.xaxis.set_major_formatter(ef0)

        ax.set(xlabel = "U[V]", ylabel = "I[A]")
        ax.grid()
        ax.legend(self.df.columns, loc = 'best')


        self.canvas.draw()


    def save_df(self):
        filename = fd.asksaveasfilename(filetypes=[("Plik CSV","*.csv")], defaultextension = "*.csv", initialfile=CFG[c_title]) 
        if filename != '':
            self.df.to_csv(filename, sep = ';', decimal = ',', index=True)
            self.print_info(f"Zapisano dane do pliku {filename}\n")

    def load_df(self):
        filename = fd.askopenfilename(filetypes=[("Plik CSV","*.csv")], defaultextension = "*.csv") 
        if filename != '':
            self.df = pd.read_csv(filename, sep = ';',  decimal = ',',  index_col=column_U) 
            self.print_info(f"Odczytano dane z pliku {filename}\n")

            path = Path(filename)
            directory = path.parent
            name = os.path.splitext(path.name)[0]
            self.title.delete(0, tk.END)
            self.title.insert(0, name)
            CFG[c_title] = name
            self.graf()



    def save_CFG(self):
        self.get_data()
        dir = os.path.dirname(os.path.realpath(__file__)) 
        #base = os.path.basename(os.path.realpath(__file__)) 
        #split= os.path.splitdrive(os.path.realpath(__file__)) 
        #print(dir)
        #print(base)
        #print(split)
        with open(dir+'\\'+ type(self).__name__+'.yaml', 'w') as file:
            yaml.dump(CFG, file)

    def send_cmd(self):
        c = f'Ta={CFG[c_Ta]}_{column_I}'

        pomiar = f'U={f(self.u)}V; I={f(self.i)}A'
        self.print_info(f"Wysłano {c} {pomiar}\n")

        self.df.at[self.u, c] = self.i

        self.graf()

    def odczyt_UI(self, rig):
        u = float(rig.query(":FUNCtion2:VALUe2?"))
        i = abs(float(rig.query(":FUNCtion2:VALUe1?")))
        return u, i

  
    def idle_time(self):
        self.get_data()
        self.after(100, self.idle_time)


    def idle_1000ms(self):
        self.get_data()
        self.timestamp = pd.Timestamp.now()
        if len(self.rigs) > 0:
            self.u, self.i = self.odczyt_UI(self.rigs[0])

            p = self.u*self.i

            Tj = CFG[c_Ta] + p * CFG[c_Rth]
            pomiar = f'U={f(self.u)}V  I={f(self.i)}A  P={f(p)}W  Tj={f(Tj)}C'
            self.pomiar["text"] = pomiar

            if Tj > CFG[c_Tj_max]:
                self.pomiar["fg"] = '#F00' 
            else:
                self.pomiar["fg"] = '#00F' 


        self.after(1000, self.idle_1000ms)



if __name__ == "__main__":
    Rigol(master=tk.Tk()).mainloop()

Dodaj komentarz