class StreamSplitter(object): def __init__(self, source_ip, source_port, listen_ip, listen_port, pdc_id=1, method="tcp", buffer_size=2048): self.pdc = Pdc(pdc_id, source_ip, source_port, buffer_size, method) self.pmu = Pmu(ip=listen_ip, port=listen_port, method=method, buffer_size=buffer_size, set_timestamp=False) self.source_cfg1 = None self.source_cfg2 = None self.source_cfg3 = None self.source_header = None def run(self): self.pdc.run() self.source_header = self.pdc.get_header() self.source_cfg2 = self.pdc.get_config() self.pdc.start() self.pmu.run() self.pmu.set_header(self.source_header) self.pmu.set_configuration(self.source_cfg2) while True: message = self.pdc.get() if self.pmu.clients and message: self.pmu.send(message) if isinstance(message, HeaderFrame): self.pmu.set_header(message) elif isinstance(message, ConfigFrame2): self.pmu.set_configuration(message)
def pmuThread(pmuID, pmu_ip, port, buffer_size, setTS): pmu = Pmu(pmu_id=int(pmuID), port=int(port), ip=pmu_ip, buffer_size=int(buffer_size), set_timestamp=setTS) # pmu.logger.setLevel("DEBUG") pmu.set_configuration( cybergridCfg ) # This will load PMU configuration specified in IEEE C37.118.2 -Annex D (Table D.2) pmu.set_header() phaseAng1 = 0 phaseAng2 = 2.09439 phaseAng3 = -2.09439 pmu.run() # PMU starts listening for incoming connections while True: try: if pmu.clients: # Check if there is any connected PDCs sleep(1 / pmu.cfg2.get_data_rate()) cybergrid_data_sample.set_phasors([(120.0, phaseAng1), (120.0, phaseAng2), (120.0, phaseAng3)]) # pmu.send_data(phasors=[(120.0, 3.14), # (120.0, 3.14), # (120.0, 3.14)], # analog=[9.91], # digital=[0x0001]) pmu.send( cybergrid_data_sample ) # Sending sample data frame specified in IEEE C37.118.2 - Annex D (Table D.1) phaseAng1 = phaseIncrem(phaseAng1) phaseAng2 = phaseIncrem(phaseAng2) phaseAng3 = phaseIncrem(phaseAng3) except EnvironmentError as e: print(e) sys.exit() pmu.join()
class StreamSplitter(object): def __init__(self, source_ip, source_port, listen_ip, listen_port, pdc_id=1, method="tcp", buffer_size=2048): self.pdc = Pdc(pdc_id, source_ip, source_port, buffer_size, method) self.pmu = Pmu(ip=listen_ip, port=listen_port, method=method, buffer_size=buffer_size, set_timestamp=False) self.source_cfg1 = None self.source_cfg2 = None self.source_cfg3 = None self.source_header = None def run(self): self.pdc.run() self.source_header = self.pdc.get_header() self.source_cfg2 = self.pdc.get_config() self.pdc.start() self.pmu.run() self.pmu.set_header(self.source_header) self.pmu.set_configuration(self.source_cfg2) while True: message = self.pdc.get() if self.pmu.clients and message: self.pmu.send(message) if isinstance(message, HeaderFrame): self.pmu.set_header(message) elif isinstance(message, ConfigFrame2): self.pmu.set_configuration(message)
60, # Nominal frequency 1, # Configuration change count 240) # Rate of phasor data transmission) hf = HeaderFrame( 7, # PMU_ID "Hello I'm nanoPMU!") # Header Message df = DataFrame( 7, # PMU_ID ("ok", True, "timestamp", False, False, False, 0, "<10", 0), # STAT WORD - Check DataFrame set_stat() [(14635, 0), (-7318, -12676), (-7318, 12675), (1092, 0)], # PHASORS (3 - v, 1 - i) 2500, # Frequency deviation from nominal in mHz 0, # Rate of Change of Frequency [100, 1000, 10000], # Analog Values [0x3c12], # Digital status word cfg) # Data Stream Configuration pmu.set_configuration(cfg) pmu.set_header(hf) pmu.run() while True: if pmu.clients: pmu.send(df) pmu.join()
from synchrophasor.pmu import Pmu """ tinyPMU will listen on ip:port for incoming connections. When tinyPMU receives command to start sending measurements - fixed (sample) measurement will be sent. """ if __name__ == "__main__": pmu = Pmu(ip="127.0.0.1", port=1410) pmu.logger.setLevel("DEBUG") pmu.set_configuration( ) # This will load default PMU configuration specified in IEEE C37.118.2 - Annex D (Table D.2) pmu.set_header( ) # This will load default header message "Hello I'm tinyPMU!" pmu.run() # PMU starts listening for incoming connections while True: if pmu.clients: # Check if there is any connected PDCs pmu.send( pmu.ieee_data_sample ) # Sending sample data frame specified in IEEE C37.118.2 - Annex D (Table D.1) pmu.join()
(0, 'i'),(0, 'i'),(0, 'i'), (0, 'i'),(0, 'i'),(0, 'i'), (0, 'i'),(0, 'i'),(0, 'i'), (0, 'i'),(0, 'i'),(0, 'i'), (0, 'i'),(0, 'i'),(0, 'i')], # Conversion factor for phasor channels [(1, 'pow'), (1, 'rms'), (1, 'peak')], # Conversion factor for analog channels [(0x0000, 0xffff)], # Mask words for digital status words 60, # Nominal frequency 1, # Configuration change count 240) # Rate of phasor data transmission) hf = HeaderFrame(7, # PMU_ID "This is the test program") # Header Message pmu.set_configuration(cfg) pmu.set_header(hf) pmu.run() sock = socket.socket(socket.AF_INET, # Internet socket.SOCK_DGRAM) # UDP sock.bind((SIMULINK_UDP_IP, SIMULINK_UDP_PORT)) while True: data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes parsData = struct.unpack('45d', data) df = DataFrame(int(parsData[0]), # PMU_ID ('ok', True, 'timestamp', False, False, False, 0, '<10', 0), # STAT WORD - Check DataFrame set_stat() [(parsData[3], parsData[4]), (parsData[5], parsData[6]), (parsData[7], parsData[8]), (parsData[9], 0),(parsData[11], 0), (parsData[13], 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0),(0, parsData[28]), (0,parsData[30]), (0,parsData[32]), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0),(0, 0)], # PHASORS (3 - v, 1 - i)
""" tinyPMU will listen on ip:port for incoming connections. When tinyPMU receives command to start sending measurements - fixed (sample) measurement will be sent. """ from synchrophasor.pmu import Pmu from multiprocessing import Process import socket import sys if __name__ == "__main__": pmu = Pmu(ip="127.0.0.3", port=1410) pmu.logger.setLevel("DEBUG") pmu.set_configuration() # This will load default PMU configuration specified in IEEE C37.118.2 - Annex D (Table D.2) pmu.set_header() # This will load default header message "Hello I'm tinyPMU!" pmu.run() # PMU starts listening for incoming connections while True: if pmu.clients: # Check if there is any connected PDCs pmu.send(pmu.ieee_data_sample) # Sending sample data frame specified in IEEE C37.118.2 - Annex D (Table D.1) pmu.join()
class MiniPMU(object): def __init__(self, name: str='', dime_address: str='ipc:///tmp/dime', pmu_idx: list=list(), max_store: int=1000, pmu_ip: str='0.0.0.0', pmu_port: int=1410, **kwargs): """ Create a MiniPMU instance for PMU data streaming over Mininet. Assumptions made for Parameters ---------- name dime_address pmu_idx max_store pmu_ip pmu_port kwargs """ assert name, 'PMU Receiver name is empty' assert pmu_idx, 'PMU idx is empty' self.name = name self.dime_address = dime_address self.pmu_idx = pmu_idx self.max_store = max_store # for recording self.max_store_record = 30 * 600 # 600 seconds self.reset = True self.pmu_configured = False self.pmu_streaming = False self.reset_var() self.dimec = Dime(self.name, self.dime_address) self.pmu = Pmu(ip=pmu_ip, port=pmu_port) def reset_var(self, retain_data=False): """ Reset flags and memory :return: None """ if not self.reset: return self.bus_name = [] self.var_idx = {'am': [], 'vm': [], 'w': [], } self.fn = 60 self.Vn = [] self.Varheader = list() self.Idxvgs = dict() self.SysParam = dict() self.SysName = dict() self.Varvgs = ndarray([]) self.t = ndarray([]) self.data = ndarray([]) self.count = 0 # recording storage if not retain_data: self.t_record = ndarray([]) self.data_record = ndarray([]) self.count_record = 0 self.counter_replay = 0 # replay index into `data_record` and `t_record` self.record_state = RecordState.IDLE self.last_data = None self.last_t = None def start_dime(self): """ Starts the dime client stored in `self.dimec` """ # logger.info('Connecting to server at {}'.format(self.dime_address)) assert self.dimec.start() # logger.info('DiME client connected') def respond_to_sim(self): """ DEPRECIATED: Respond with data streaming configuration to the simulator :return: None """ pass def get_bus_name(self): """ Return bus names based on ``self.pmu_idx`` and store bus names to ``self.bus_name`` :return: list of bus names """ # assign generic bus names self.bus_name = list(self.pmu_idx) for i in range(len(self.bus_name)): self.bus_name[i] = 'Bus_' + str(self.bus_name[i]) # assign names from SysName if present if len(self.SysName) > 0: for i in range(len(self.bus_name)): self.bus_name[i] = self.SysName['Bus'][self.pmu_idx[i] - 1] # logger.debug('PMU names changed to: {}'.format(self.bus_name)) return self.bus_name def get_bus_Vn(self): """ Retrieve Bus.Vn Returns ------- """ self.Vn = [1] * len(self.pmu_idx) for i, idx in enumerate(self.pmu_idx): self.Vn[i] = self.SysParam['Bus'][idx][1] * 1000 # get Vn # logger.info('Retrieved bus Vn {}'.format(self.Vn)) def config_pmu(self): """ Sets the ConfigFrame2 of the PMU :return: None """ self.cfg = ConfigFrame2(pmu_id_code=self.pmu_idx[0], # PMU_ID time_base=1000000, # TIME_BASE num_pmu=1, # Number of PMUs included in data frame station_name=self.bus_name[0], # Station name id_code=self.pmu_idx[0], # Data-stream ID(s) data_format=(True, True, True, True), # Data format - POLAR; PH - REAL; AN - REAL; FREQ - REAL; phasor_num=1, # Number of phasors analog_num=1, # Number of analog values digital_num=1, # Number of digital status words channel_names=["V_PHASOR", "ANALOG1", "BREAKER 1 STATUS", "BREAKER 2 STATUS", "BREAKER 3 STATUS", "BREAKER 4 STATUS", "BREAKER 5 STATUS", "BREAKER 6 STATUS", "BREAKER 7 STATUS", "BREAKER 8 STATUS", "BREAKER 9 STATUS", "BREAKER A STATUS", "BREAKER B STATUS", "BREAKER C STATUS", "BREAKER D STATUS", "BREAKER E STATUS", "BREAKER F STATUS", "BREAKER G STATUS"], # Channel Names ph_units=[(0, 'v')], # Conversion factor for phasor channels - (float representation, not important) an_units=[(1, 'pow')], # Conversion factor for analog channels dig_units=[(0x0000, 0xffff)], # Mask words for digital status words f_nom=60.0, # Nominal frequency cfg_count=1, # Configuration change count data_rate=30) # Rate of phasor data transmission) self.hf = HeaderFrame(self.pmu_idx[0], # PMU_ID "MiniPMU <{name}> {pmu_idx}".format(name=self.name, pmu_idx = self.pmu_idx)) # Header Message self.pmu.set_configuration(self.cfg) self.pmu.set_header(self.hf) # self.pmu.run() def find_var_idx(self): """ Returns a dictionary of the indices into Varheader based on `self.pmu_idx`. Items in `self.pmu_idx` uses 1-indexing. For example, if `self.pmu_idx` == [1, 2], this function will return the indices of - Idxvgs.Pmu.vm[0] and Idxvgs.Pmu.vm[1] as vm - Idxvgs.Pmu.am[0] and Idxvgs.Pmu.am[1] as am - Idxvgs.Bus.w_Busfreq[0] and Idxvgs.Bus.w_Busfreq[1] as w in the dictionary `self. var_idx` with the above fields. :return: ``var_idx`` in ``pmudata`` """ npmu = len(self.Idxvgs['Pmu']['vm'][0]) self.var_idx['vm'] = [int(i) - 1 for i in self.pmu_idx] self.var_idx['am'] = [npmu + int(i) - 1 for i in self.pmu_idx] self.var_idx['w'] = [2 * npmu + int(i) - 1 for i in self.pmu_idx] # TODO: make it static @property def vgsvaridx(self): return array(self.var_idx['vm'] + self.var_idx['am'] + self.var_idx['w'], dtype=int) def init_storage(self, flush=False): """ Initialize data storage `self.t` and `self.data` :return: if the storage has been reset """ # TODO: make it more efficient?? ret = False if self.count % self.max_store == 0: self.t = zeros(shape=(self.max_store, 1), dtype=float) self.data = zeros(shape=(self.max_store, len(self.pmu_idx * 3)), dtype=float) self.count = 0 ret = True else: ret = False if (self.count_record % self.max_store_record == 0) or (flush is True): self.t_record = zeros(shape=(self.max_store_record, 1), dtype=float) self.data_record = zeros(shape=(self.max_store_record, len(self.pmu_idx * 3)), dtype=float) self.count_record = 0 self.counter_replay = 0 ret = ret and True else: ret = False return ret def sync_and_handle(self): """ Sync and call data processing functins :return: """ ret = False var = self.dimec.sync() if var is False or None: return ret # if self.reset is True: # logger.info('[{name}] variable <{var}> synced.' # .format(name=self.name, var=var)) data = self.dimec.workspace[var] if var in ('SysParam', 'Idxvgs', 'Varheader'): # only handle these three variables during reset cycle if self.reset is True: self.__dict__[var] = data # else: # logger.info('{} not handled outside reset cycle'.format(var)) elif var == 'pmudata': # only handle pmudata during normal cycle if self.reset is False: # logger.info('In, t={:.4f}'.format(data['t'])) self.handle_measurement_data(data) # else: # logger.info('{} not handled during reset cycle'.format(var)) # handle SysName any time elif var == 'SysName': self.__dict__[var] = data self.get_bus_name() elif var == 'DONE' and data == 1: self.reset = True self.reset_var(retain_data=True) elif var == 'pmucmd' and isinstance(data, dict): cmd = '' if data.get('record', 0) == 1: # start recording if self.record_state == RecordState.IDLE \ or self.record_state == RecordState.RECORDED: self.record_state = RecordState.RECORDING cmd = 'start recording' # else: # logger.warning('cannot start recording in state {}' # .format(self.record_state)) elif data.get('record', 0) == 2: # stop recording if started if self.record_state == RecordState.RECORDING: cmd = 'stop recording' self.record_state = RecordState.RECORDED # else: # logger.warning('cannot stop recording in state {}' # .format(self.record_state)) if data.get('replay', 0) == 1: # start replay if self.record_state == RecordState.RECORDED: cmd = 'start replay' self.record_state = RecordState.REPLAYING # else: # logger.warning('cannot start replaying in state {}' # .format(self.record_state)) if data.get('replay', 0) == 2: # stop replay but retain the saved data if self.record_state == RecordState.REPLAYING: cmd = 'stop replay' self.record_state = RecordState.RECORDED # else: # logger.warning('cannot stop replaying in state {}' # .format(self.record_state)) if data.get('flush', 0) == 1: # flush storage cmd = 'flush storage' self.init_storage(flush=True) self.record_state = RecordState.IDLE # if cmd: # logger.info('[{name}] <{cmd}>'.format(name=self.name, cmd=cmd)) # else: # logger.info('[{name}] {cmd} not handled during normal ops' # .format(name=self.name, cmd=var)) return var def handle_measurement_data(self, data): """ Store synced data into self.data and return in a tuple of (t, values) :return: (t, vars) """ self.init_storage() self.data[self.count, :] = data['vars'][0, self.vgsvaridx].reshape(-1) self.t[self.count, :] = data['t'] self.count += 1 # record if self.record_state == RecordState.RECORDING: self.data_record[self.count_record, :] = \ data['vars'][0, self.vgsvaridx].reshape(-1) self.t_record[self.count_record, :] = data['t'] self.count_record += 1 self.last_data = data['vars'] self.last_t = data['t'] return data['t'], data['vars'] def run(self): """ Process control function :return None """ self.start_dime() self.pmu.run() while True: if self.reset is True: # receive init and respond # logger.info('[{name}] Entering reset mode..' # .format(name=self.name)) while True: var = self.sync_and_handle() if var is False: time.sleep(0.01) if len(self.Varheader) > 0\ and len(self.Idxvgs) > 0\ and len(self.SysParam) > 0 \ and len(self.SysName) > 0: self.find_var_idx() self.get_bus_Vn() break self.respond_to_sim() if self.pmu_configured is False: self.config_pmu() self.pmu_configured = True self.reset = False # logger.debug('Entering sync and short sleep...') var = self.sync_and_handle() time.sleep(0.001) if var is False: continue elif var == 'pmudata': if self.pmu.clients and not self.reset: if self.record_state == RecordState.REPLAYING: # prepare recorded data npmu = len(self.pmu_idx) v_mag = self.data_record[self.counter_replay, :npmu] * self.Vn[0] v_ang = wrap_angle(self.data_record[self.counter_replay, npmu:2*npmu]) v_freq = self.data_record[self.counter_replay, 2*npmu:3*npmu] * self.fn self.counter_replay += 1 # at the end of replay, reset if self.counter_replay == self.count_record: self.counter_replay = 0 self.record_state = RecordState.RECORDED else: # use fresh data v_mag = self.last_data[0, self.var_idx['vm']] * self.Vn[0] v_ang = wrap_angle(self.last_data[0, self.var_idx['am']]) v_freq = self.last_data[0, self.var_idx['w']] * self.fn # TODO: add noise to data try: # TODO: fix multiple measurement (multi-bus -> one PMU case) self.pmu.send_data(phasors=[(v_mag, v_ang)], analog=[9.99], digital=[0x0001], #freq=(v_freq-60)*1000 freq = v_freq ) # logger.info('Out, f={f:.5f}, vm={vm:.1f}, am={am:.2f}'.format(f=v_freq[0], vm=v_mag[0], am=v_ang[0])) except Exception as e: logger.exception(e)
cybergrid_data_sample = DataFrame( 1, ("ok", True, "timestamp", False, False, False, 0, "<10", 0), [(14635, 0), (-7318, -3.14), (-7318, 3.14)], 30, 0, [100], [0x3c12], cybergridCfg) if exec_type == "PMU": pmu = Pmu(pmu_id=int(pmuID), port=int(port), ip=pmu_ip, buffer_size=int(buffer_size), set_timestamp=setTS) pmu.set_configuration( cybergridCfg ) # This will load PMU configuration specified in IEEE C37.118.2 -Annex D (Table D.2) phaseAng1 = -1.13 phaseAng2 = 3.14 / 2 phaseAng3 = -3.14 pmu.run() # PMU starts listening for incoming connections # setPDC(pmuID,pmu_ip,port) while True: try: if pmu.clients: if pmu.listener: # Check if there is any connected PDCs sleep(1 / pmu.cfg2.get_data_rate()) #print(phaseAng1, phaseAng2, phaseAng3) cybergrid_data_sample.set_phasors([(120.0, phaseAng1), (120.0, phaseAng2),
class MyPmu: ''' Implements the communication protocol of the IEEE C37.118 synchrofphasor standard (IEC 61850) using "pypmu" lib (synchrophasor in the imports). This class acts like a wrapper interfacing the lib. Uses a callback function to handle the PPS as a trigger event on the GPIO 18 of the raspberry. ''' def __init__(self, channelNames, nFreq = 50): ''' channelNames = list of the Channels' Names nFreq = Nominal Frequency is needed for the configuration frame! ''' self.pmu = Pmu(ip="127.0.0.1", port=1411) self.pmu.logger.setLevel("DEBUG") self.nFreq = nFreq ph_v_conversion = [(100000, "v")]*len(channelNames) # Voltage phasor conversion factor self.cfg = ConfigFrame2(7, # PMU_ID 1000000, # TIME_BASE 1, # Number of PMUs included in data frame "Station A", # Station name 7734, # Data-stream ID(s) 15, # Data format - Check ConfigFrame2 set_data_format() len(channelNames), # Number of phasors 0, # Number of analog values 0, # Number of digital status words channelNames, # Channel Names ph_v_conversion, # Conversion factor for phasor channels [], # Conversion factor for analog channels [], # Mask words for digital status words nFreq, # Nominal frequency 1, # Configuration change count 1) # Rate of phasor data transmission) self.hf = HeaderFrame(7, # PMU_ID "Hello I'm MyPMU!") # Header Message self.current_dataframe = None self.pmu.set_configuration(self.cfg) self.pmu.set_header(self.hf) def run(self): ''' Create TCP socket, bind port and listen for incoming connections ''' self.pmu.run() while True: pass self.pmu.join() def set_dataframe(self, synchrophasors, soc): ''' Sets the new dataframe to be sent. ''' sph = [] rocof = [] freq_dev = [] for chan in synchrophasors: #for every chan is given a synchrophasor sph.append((synchrophasors[chan]['amplitude'], synchrophasors[chan]['phase'])) if abs(self.nFreq-synchrophasors[chan]['avg_freq']) > 32.767: rocof.append(0) else: rocof.append(synchrophasors[chan]['rocof']) #1 rocof for the whole datagram, the first is given freq_dev.append(synchrophasors[chan]['avg_freq']-self.nFreq)# average frequency deviation from nominal if len(freq_dev) == 0: freq_dev.append(0) self.current_dataframe = DataFrame(7, # PMU_ID ("ok", True, "timestamp", False, False, False, 0, "<10", 0), # STAT WORD - Check DataFrame set_stat() sph, # PHASORS np.average(freq_dev), # Frequency deviation from nominal in mHz np.average(rocof), # Rate of Change of Frequency [], # Analog Values [], # Digital status word self.cfg, # Data Stream Configuration soc=soc) def send(self, redlab, sph, timestamp): ''' This interface function for the lib sets the dataframe with the given arguments and if the PDC is connected start the communication. ''' myPmu.set_dataframe(sph, timestamp) if myPmu.pmu.clients: #if PDC asked for frame / is connected myPmu.pmu.send(myPmu.current_dataframe)