""" Wrapper dla ZDistA_komp.dll - algorytm zabezpieczenia odległościowego Używa natywnej biblioteki C zamiast implementacji Python """ import ctypes import numpy as np import math import os # Ścieżka do DLL DLL_PATH = os.path.join(os.path.dirname(__file__), 'ZDistA_komp.dll') # Ładowanie DLL try: _dll = ctypes.CDLL(DLL_PATH) _dll_loaded = True except OSError as e: print(f"OSTRZEŻENIE: Nie można załadować DLL: {e}") print("Używam implementacji Python") _dll_loaded = False # Definicje funkcji DLL jeśli załadowano if _dll_loaded: # ZDistA_init(z1_r, z1_x, line_angle_deg, kierunek) -> handle _dll.ZDistA_init.argtypes = [ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_int] _dll.ZDistA_init.restype = ctypes.c_void_p # ZDistA_step(handle, u_re[3], u_im[3], i_re[3], i_im[3]) -> int _dll.ZDistA_step.argtypes = [ ctypes.c_void_p, ctypes.POINTER(ctypes.c_float), # u_re[3] ctypes.POINTER(ctypes.c_float), # u_im[3] ctypes.POINTER(ctypes.c_float), # i_re[3] ctypes.POINTER(ctypes.c_float) # i_im[3] ] _dll.ZDistA_step.restype = ctypes.c_int # ZDistA_cleanup(handle) _dll.ZDistA_cleanup.argtypes = [ctypes.c_void_p] _dll.ZDistA_cleanup.restype = None class DistanceRelayZDLL: """ Algorytm zabezpieczenia odległościowego - wersja DLL Wykorzystuje natywną bibliotekę ZDistA_komp.dll """ def __init__(self, Z_line_R=2.0, Z_line_X=8.0, line_angle=75.0, z1_reach=0.8, z2_reach=1.2, z3_reach=1.5, z4_reach=2.0, z5_reach=2.5, t_z1=0, t_z2=300, t_z3=600, t_z4=1000, t_z5=1500, przekladnia=1.0, fi1=75.0, fi2=85.0, fi3=90.0, fi4=90.0, I_min=0.5, U_min=1.0, kierunek=2, Kk1=0.0, Kk1_kat=0.0, KkC=0.0, KkC_kat=0.0, typ_zwarc=0, wyl=False, settings=None): if not _dll_loaded: raise RuntimeError("DLL nie została załadowana!") # Parametry linii self.Z_line_R = Z_line_R self.Z_line_X = Z_line_X self.Z_line_mag = np.sqrt(Z_line_R**2 + Z_line_X**2) self.line_angle = line_angle # Przekładnia self.przekladnia = przekladnia # Kąty charakterystyki self.fi1 = fi1 self.fi2 = fi2 self.fi3 = fi3 self.fi4 = fi4 # Współczynniki stref self.z1_reach = z1_reach self.z2_reach = z2_reach self.z3_reach = z3_reach self.z4_reach = z4_reach self.z5_reach = z5_reach # Oblicz R i X dla stref self.Z1_R = Z_line_R * z1_reach self.Z1_X = Z_line_X * z1_reach self.Z2_R = Z_line_R * z2_reach self.Z2_X = Z_line_X * z2_reach self.Z3_R = Z_line_R * z3_reach self.Z3_X = Z_line_X * z3_reach self.Z4_R = Z_line_R * z4_reach self.Z4_X = Z_line_X * z4_reach self.Z5_R = Z_line_R * z5_reach self.Z5_X = Z_line_X * z5_reach # Opóźnienia stref [ms] self.t_z1 = t_z1 self.t_z2 = t_z2 self.t_z3 = t_z3 self.t_z4 = t_z4 self.t_z5 = t_z5 # Minimalne prądy i napięcia self.I_min = I_min self.U_min = U_min self.Igr = I_min * I_min # Kierunek self.kierunek = kierunek # Nadpisz nastawy z pliku konfiguracyjnego if settings: print("\n--- Nadpisywanie nastaw ZDistA DLL z pliku konfiguracyjnego ---") self.kierunek = int(settings.get('Kierunek', self.kierunek)) if 'Z1_R' in settings and self.Z_line_R > 0: self.z1_reach = float(settings['Z1_R']) / self.Z_line_R self.Z1_R = float(settings['Z1_R']) self.Z1_X = self.Z_line_X * self.z1_reach if 'Z2_R' in settings and self.Z_line_R > 0: self.z2_reach = float(settings['Z2_R']) / self.Z_line_R self.Z2_R = float(settings['Z2_R']) self.Z2_X = self.Z_line_X * self.z2_reach if 'Z3_R' in settings and self.Z_line_R > 0: self.z3_reach = float(settings['Z3_R']) / self.Z_line_R self.Z3_R = float(settings['Z3_R']) self.Z3_X = self.Z_line_X * self.z3_reach self.t_z1 = int(settings.get('tZ1', self.t_z1)) self.t_z2 = int(settings.get('tZ2', self.t_z2)) self.t_z3 = int(settings.get('tZ3', self.t_z3)) print("--- Koniec nadpisywania nastaw ZDistA DLL ---\n") # Inicjalizacja DLL # Używamy Z1_R i Z1_X jako nastawy bazowej self.handle = _dll.ZDistA_init( ctypes.c_double(self.Z1_R), ctypes.c_double(self.Z1_X), ctypes.c_double(line_angle), ctypes.c_int(kierunek) ) if not self.handle: raise RuntimeError("Nie udało się zainicjalizować ZDistA DLL") # Debug info print(f"Nastawy zabezpieczenia ZDistA (DLL):") print(f" Linia: R={Z_line_R:.2f} Ohm, X={Z_line_X:.2f} Ohm, |Z|={self.Z_line_mag:.2f} Ohm") print(f" Kąt linii: {line_angle:.1f} deg") print(f" Strefa 1: {self.z1_reach*100:.0f}% (R={self.Z1_R:.2f}, X={self.Z1_X:.2f})") print(f" Strefa 2: {self.z2_reach*100:.0f}% (R={self.Z2_R:.2f}, X={self.Z2_X:.2f})") print(f" Strefa 3: {self.z3_reach*100:.0f}% (R={self.Z3_R:.2f}, X={self.Z3_X:.2f})") print(f" Kierunek: {self.kierunek} (0=bez, 1=do linii, 2=do szyn)") print(f" DLL zainicjalizowany pomyślnie") # Stan wyłącznika self.wyl = wyl self.tripped = { 'L1': False, 'L2': False, 'L3': False, 'L1L2': False, 'L2L3': False, 'L3L1': False } def init_relay(self): """Inicjalizacja zabezpieczenia""" print("Zabezpieczenie ZDistA (DLL) zainicjalizowane") def reset(self): """Reset stanów""" self.tripped = { 'L1': False, 'L2': False, 'L3': False, 'L1L2': False, 'L2L3': False, 'L3L1': False } def step_relay(self, phase, u_re, u_im, i_re, i_im, u0_re=0, u0_im=0, i0_re=0, i0_im=0, u_zg_re=0, u_zg_im=0, i_zg_re=0, i_zg_im=0): """ Krok algorytmu dla jednej fazy phase: 'L1', 'L2' lub 'L3' Zwraca: 0 = brak trip, 1 = trip """ if not self.handle: return 0 # Mapowanie fazy na indeks (DLL oczekuje wszystkich 3 faz na raz) phase_map = {'L1': 0, 'L2': 1, 'L3': 2} # Przygotuj tablice dla DLL (3 fazy) u_re_arr = (ctypes.c_float * 3)() u_im_arr = (ctypes.c_float * 3)() i_re_arr = (ctypes.c_float * 3)() i_im_arr = (ctypes.c_float * 3)() # Wypełnij dane dla wszystkich 3 faz # Dla uproszczenia, używamy tych samych wartości dla wszystkich faz # (w rzeczywistości tester.py przekazuje różne wartości dla każdej fazy) u_re_arr[0] = u_re u_im_arr[0] = u_im i_re_arr[0] = i_re i_im_arr[0] = i_im # Dla pozostałych faz używamy 0 (lub moglibyśmy przekazać dane z innych faz) # To uproszczenie - w pełnej wersji trzeba przekazać wszystkie 3 fazy naraz u_re_arr[1] = 0.0 u_im_arr[1] = 0.0 i_re_arr[1] = 0.0 i_im_arr[1] = 0.0 u_re_arr[2] = 0.0 u_im_arr[2] = 0.0 i_re_arr[2] = 0.0 i_im_arr[2] = 0.0 # Wywołaj funkcję DLL result = _dll.ZDistA_step( self.handle, u_re_arr, u_im_arr, i_re_arr, i_im_arr ) # Zaktualizuj stan trip if result > 0: self.tripped[phase] = True return result def __del__(self): """Destruktor - zwolnij pamięć DLL""" if hasattr(self, 'handle') and self.handle: _dll.ZDistA_cleanup(self.handle) self.handle = None