class Streaming: """ ANDES data streaming class to interface with CURENT LTB. """ def __init__(self, system): self.system = system self.params_built = False self.SysParam = dict() self.SysName = dict() self.Idxvgs = dict() self.ModuleInfo = dict() self.Varheader = list() self.last_devices = list() self.has_pmu = False self.dimec = None def connect(self): """ Connect to DiME 2 server. If ``dime_address`` is specified from the command-line, streaming will be automatically enabled. Otherwise, settings from the Config file will be used. """ config = self.system.config options = self.system.options # enable only when both arguments are supplied if options.get("dime_address") is not None: config.dime_enabled = True config.dime_address = options.get("dime_address") if not config.dime_enabled: return False try: self.dimec = DimeClient(config.dime_address) self.dimec.join(config.dime_name) logger.info('Dime connection to "%s" was successful.', config.dime_address) return True except NameError: logger.error( 'Dime not installed. Set System config `dime_enabled` to `0` to suppress warning.' ) self.system.config.dime_enabled = False except FileNotFoundError: logger.error('Dime sever not found at "%s".', config.dime_address) self.system.config.dime_enabled = False return False def _build_SysParam(self): self.SysParam = self.system.as_dict(vin=True, skip_empty=True) self.params_built = True def _build_SysName(self): self.SysName['Bus'] = self.system.Bus.name.v if self.system.Area.n: self.SysName['Areas'] = self.system.Area.name.v def _build_Varheader(self): self.Varheader = self.system.dae.xy_name def _build_Idxvgs(self): m = self.system.dae.m n = self.system.dae.n mn = m + n # NOQA self.Idxvgs['System'] = { 'nBus': self.system.Bus.n, 'nLine': self.system.Line.n, } self.Idxvgs['Bus'] = { 'theta': 1 + n + self.system.Bus.a.a, 'V': 1 + n + self.system.Bus.v.a, 'w_Busfreq': 1 + n + self.system.BusFreq.f.a, # NO LONGER SUPPORTED # 'P': 1 + mn + array(range(self.system.Bus.n)), # 'Q': 1 + mn + self.system.Bus.n + array(range(self.system.Bus.n)), } self.Idxvgs['Pmu'] = { # NOT YET SUPPORTED 'vm': 1 + self.system.PMU.vm.a, 'am': 1 + self.system.PMU.am.a, } # NOT YET SUPPORTED # line0 = 1 + mn + 2 * self.system.Bus.n self.Idxvgs['Line'] = { # 'Pij': line0 + array(range(self.system.Line.n)), # 'Pji': line0 + self.system.Line.n + array(range(self.system.Line.n)), # 'Qij': line0 + 2 * self.system.Line.n + array(range(self.system.Line.n)), # 'Qji': line0 + 3 * self.system.Line.n + array(range(self.system.Line.n)), } self.Idxvgs['Syn'] = { 'delta': 1 + np.append(self.system.GENCLS.delta.a, self.system.GENROU.delta.a), 'omega': 1 + np.append(self.system.GENCLS.omega.a, self.system.GENROU.omega.a), 'e1d': 1 + np.append([0] * self.system.GENCLS.n, self.system.GENROU.e1d.a), 'e1q': 1 + np.append([0] * self.system.GENCLS.n, self.system.GENROU.e1q.a), 'e2d': 1 + np.append([0] * self.system.GENCLS.n, self.system.GENROU.e2d.a), 'e2q': 1 + np.append([0] * self.system.GENCLS.n, self.system.GENROU.e2q.a), 'psid': 1 + np.append([0] * self.system.GENCLS.n, self.system.GENROU.psid.a), 'psiq': 1 + np.append([0] * self.system.GENCLS.n, self.system.GENROU.psiq.a), # NOT SUPPORTED # 'p': 1 + n + array([0] * self.system.GENCLS.n + self.system.GENROU.p.a), # 'q': 1 + n + array([0] * self.system.GENCLS.n + self.system.GENROU.q.a), } self.Idxvgs['Tg'] = { 'pm': 1 + n + self.system.TG2.pout.a, 'wref': 1 + n + self.system.TG2.wref.a, } self.Idxvgs['Exc'] = { # NOT YET READY # 'vf': # 1 + n + array(self.system.AVR1.vfout + self.system.AVR2.vfout + # self.system.AVR3.vfout), # 'vm': # 1 + array(self.system.AVR1.vm + self.system.AVR2.vm + # self.system.AVR3.vm), } # NOT YET READY # if self.system.WTG3.n: # self.Idxvgs['Dfig'] = { # 'omega_m': 1 + array(self.system.WTG3.omega_m), # 'theta_p': 1 + array(self.system.WTG3.theta_p), # 'idr': 1 + array(self.system.WTG3.ird), # 'iqr': 1 + array(self.system.WTG3.irq), # } # if self.system.Node.n: # self.Idxvgs['Node'] = {'v': 1 + n + array(self.system.Node.v)} # # dev_id = { # 1: 'R', # 2: 'C', # 3: 'L', # 4: 'RCp', # 5: 'RCs', # 6: 'RLCp', # 7: 'RLCs', # 8: 'RLs' # } # if 'DCLine' in self.SysParam: # DCLine_types = set(self.SysParam['DCLine'][:, 2]) # idx = [] # for item in DCLine_types: # item = int(item) # idx.extend(self.system.__dict__[dev_id[item]].Idc) # self.Idxvgs['DCLine'] = {'Idc': 1 + array(idx)} # else: # DCLine_types = () # # self.Idxvgs['DCLine'] = {} def _build_list(self, model, params, ret=None): if not ret: ret = [] else: ret = list(ret) for p in params: if type(p) in (int, float): ret.append([p] * len(ret[0])) elif type(p) == list: assert len(p) == len(ret[0]) ret.append(p) else: val = list(self.system.__dict__[model].__dict__[p]) # make sure val does not contain list if isinstance(val[0], list): logger.warning( '{}.{} contains list. Reset to zeros.'.format( model, p)) val = [0] * len(val) ret.append(val) return ret def _find_pos(self, model, fkey, src_col=0): """Find the positions of foreign keys in the source model index list""" if type(fkey) == ndarray: fkey = fkey.tolist() elif type(fkey) in (int, float): fkey = [fkey] ret = [] model_idx_list = self.SysParam[model][:, src_col].tolist() for item in fkey: ret.append( model_idx_list.index(item) if item in model_idx_list else 0) return ret def build_init(self): """ Build `Varheader`, `Idxvgs` and `SysParam` after power flow routine """ self._build_SysParam() self._build_SysName() self._build_Idxvgs() self._build_Varheader() def send_init(self, recepient='all'): """ Broadcast `Varheader`, `Idxvgs` and `SysParam` to all DiME clients after power flow routine """ if not self.system.config.dime_enabled: return if not self.params_built: self.build_init() if recepient == 'all': self.last_devices = self.dimec.devices() logger.debug('Connected modules are: ' + ','.join(self.dimec.devices())) logger.debug( 'Broadcasting Varheader, Idxvgs, SysParam and SysName...') sleep(0.05) self.dimec.broadcast_r(Varheader=self.Varheader) sleep(0.05) self.dimec.broadcast_r(Idxvgs=self.Idxvgs) sleep(0.05) try: self.dimec.broadcast_r(SysParam=self.SysParam) self.dimec.broadcast_r(SysName=self.SysName) except: # NOQA logger.warning('SysParam or SysName broadcast error.' ' Check bus coordinates.') sleep(0.5) else: if type(recepient) != list: recepient = [recepient] for item in recepient: self.dimec.send_r(item, Varheader=self.Varheader) self.dimec.send_r(item, Idxvgs=self.Idxvgs) self.dimec.send_r(item, SysParam=self.SysParam) self.dimec.send_r(item, SysName=self.SysName) def record_module_init(self, name, init_var): """ Record the variable requests from modules """ ivar = dict(init_var) var_idx = ivar['vgsvaridx'] ivar['lastk'] = 0 if name not in self.ModuleInfo: self.ModuleInfo[name] = {} if isinstance(var_idx, int): var_idx = array(var_idx, dtype=int) elif isinstance(var_idx, ndarray): var_idx = var_idx.tolist() # unwrap if nested if isinstance(var_idx[0], list): var_idx = array(var_idx[0], dtype=int) else: var_idx = array(var_idx, dtype=int) ivar['vgsvaridx'] = (var_idx - 1).tolist() ivar['lastk'] = 0 self.ModuleInfo[name].update(ivar) logger.debug('Module <%s> requests index %s', name, var_idx) @staticmethod def transpose_matlab_row(a): if type(a) is ndarray: if a.shape[0] == 1: a = a[0] return a def handle_alter(self, Alter): """Handle parameter altering""" pass def handle_event(self, Event): """Handle Fault, Breaker, Syn and Load Events""" fields = ('name', 'id', 'action', 'time', 'duration') for key in fields: if key not in Event: logger.warning('Event has missing key {}.'.format(key)) return names = self.transpose_matlab_row(Event.get('name')) idxes = self.transpose_matlab_row(Event.get('id')) actions = self.transpose_matlab_row(Event.get('action')) times = self.transpose_matlab_row(Event.get('time')) durations = self.transpose_matlab_row(Event.get('duration')) n = len(names) for i in range(n): try: name = names[i] idx = idxes[i] action = actions[i] # NOQA time = times[i] duration = durations[i] except IndexError: logger.warning( 'Event key values might have different lengths.') continue if time == -1: time = max(self.system.dae.t, 0) + self.system.tds.config.tstep tf = time + duration if duration == 0.: tf = 9999 if name.lower() == 'bus': param = {'tf': time, 'tc': tf, 'bus': idx} self.system.Fault.insert(**param) logger.debug( 'Event <Fault> added for bus %s at t = %.6g and tf = %g', idx, time, tf) elif name.lower() == 'line': bus = self.system.Line.get_field( 'bus1', ['Line_' + str(int(idx - 1))])[0] param = { 'line': 'Line_' + str(idx - 1), 'bus': bus, 't1': time, 't2': tf, 'u1': 1, 'u2': 1 if duration else 0, } self.system.Breaker.insert(**param) logger.debug( 'Event <Breaker> added for line %s at t = %.6g and tf = %g', idx, time, tf) self.system.call.build_vec() self.system.call._compile_int() self.system.dae.rebuild = True def sync_and_handle(self): """ Sync until the queue is empty. Handle sync'ed commands. """ if not self.system.config.dime_enabled: return current_devices = self.dimec.devices() # record MiniPMU if not self.has_pmu: for item in current_devices: if item.startswith('PMU_'): self.has_pmu = True # send Varheader, SysParam and Idxvgs to modules on the fly if set(current_devices) != set(self.last_devices): new_devices = list(current_devices) new_devices.remove(self.system.config.dime_name) for item in self.last_devices: if item in new_devices: new_devices.remove(item) self.send_init(new_devices) self.last_devices = current_devices while True: var_names = self.dimec.sync(1) if not var_names: break workspace = self.dimec.workspace for var_name in var_names: var_value = workspace[var_name] if var_name in current_devices: self.record_module_init(var_name, var_value) elif var_name == 'Event': self.handle_event(var_value) else: logger.warning( 'Synced variable {} not handled'.format(var_name)) def vars_to_pmu(self): """ Broadcast all PMU measurements and BusFreq measurements in the variable `pmudata` """ if not self.system.config.dime_enabled: return if not self.has_pmu: return idx = np.concatenate(( self.system.PMU.vm.a, self.system.PMU.am.a, self.system.dae.n + self.system.BusFreq.f.a, )) t = self.system.dae.t.tolist() k = 0 # field `k` is not no use values = self.system.dae.xy[ idx] # a 1-d array as opposed to a N-by-1 2-d matrix pmudata = { 't': t, 'k': k, 'vars': values, } self.dimec.broadcast_r(pmudata=pmudata) def vars_to_modules(self): """ Stream the results from the last step to modules :return: None """ if not self.system.config.dime_enabled: return for mod in self.ModuleInfo.keys(): # skip PMU modules in this function. offload it to vars_to_pmu() if mod.startswith('PMU_'): continue limitsample = self.ModuleInfo[mod].get('limitsample', 0) idx = self.ModuleInfo[mod]['vgsvaridx'] t = self.system.dae.t.tolist() k = 0 lastk = self.ModuleInfo[mod]['lastk'] if limitsample: every = 1 / self.system.tds.config.tstep / limitsample if (k - lastk) / every < 1: continue else: self.ModuleInfo[mod]['lastk'] = k values = self.system.dae.xy[idx] Varvgs = { 't': t, 'k': k, 'vars': values, 'accurate': values, } self.dimec.send_r(mod, Varvgs=Varvgs) logger.debug("Send Varvgs to module <%s>", mod) def finalize(self): """ Send ``DONE`` signal when simulation completes :return: None """ if not self.system.config.dime_enabled: return self.system.streaming.dimec.broadcast_r(DONE=1) self.system.streaming.dimec.close()
raise RuntimeError() d1 = DimeClient("ipc", sys.argv[1]) d2 = DimeClient("ipc", sys.argv[1]) d3 = DimeClient("ipc", sys.argv[1]) d1.join("d1") d2.join("d2") d1["a"] = np.random.rand(500, 500) d1["b"] = np.random.rand(500, 500) d1["c"] = np.random.rand(500, 500) d1.broadcast("a", "b", "c") d2.sync() assert np.array_equal(d1["a"], d2["a"]) assert np.array_equal(d1["b"], d2["b"]) assert np.array_equal(d1["c"], d2["c"]) d3.sync() assert np.array_equal(d1["a"], d3["a"]) assert np.array_equal(d1["b"], d3["b"]) assert np.array_equal(d1["c"], d3["c"]) d1["a"] = None d1["b"] = None d1["c"] = None
d1["a"] = np.random.rand(500, 500) d1["b"] = np.random.rand(500, 500) d1["c"] = np.random.rand(500, 500) d1["d"] = np.random.rand(500, 500) d1["e"] = np.random.rand(500, 500) d2["a"] = None d2["b"] = None d2["c"] = None d2["d"] = None d2["e"] = None d1.send("d2", "a", "b", "c") d2.sync(2) assert np.array_equal(d1["a"], d2["a"]) assert np.array_equal(d1["b"], d2["b"]) assert not np.array_equal(d1["c"], d2["c"]) assert not np.array_equal(d1["d"], d2["d"]) assert not np.array_equal(d1["e"], d2["e"]) d1.send("d2", "d", "e") d2.sync() assert np.array_equal(d1["a"], d2["a"]) assert np.array_equal(d1["b"], d2["b"]) assert np.array_equal(d1["c"], d2["c"]) assert np.array_equal(d1["d"], d2["d"]) assert np.array_equal(d1["e"], d2["e"])
class MiniPDC(object): """A MiniPDC connecting to multiple PMUs and a DiME server """ def __init__(self, name, protocol, dime_address, ip_list, port_list=None, dime_port=None, loglevel=logging.INFO): self._name = name self._dime_address = dime_address self._loglevel = loglevel self.dimec = DimeClient(protocol, dime_address, dime_port) self.dimec.join("ISLANDING") self.ip_list = ip_list self.port_list = port_list # not being used now # check if the lengths of `ip_list` and `port_list` match self.pdc = {} self.header = {} self.config = {} self.last_var = None # state flags self.andes_online = False # self.pdc_started = False @property def npmu(self): return len(self.ip_list) def initialize(self): """ Reset or initialize, it is the same thing Returns ------- """ pass def sync_and_handle(self): """ Sync from DiME and handle the received data """ self.last_var = self.dimec.sync(1) if len(self.last_var) == 0: return None self.last_var = list(self.last_var)[0] val = self.dimec.workspace[self.last_var] if self.last_var == 'DONE' and int(val) == 1: self.andes_online = False self.initialize() pass return self.last_var def start_dime(self): logger.info('Connecting to DiME at {}'.format(self._dime_address)) logger.info('DiME connected') def init_pdc(self): for idx, item in enumerate(self.ip_list): pmu_idx = int(item.split('.')[3]) self.pdc[idx] = Pdc(pdc_id=pmu_idx, pmu_ip=self.ip_list[idx], pmu_port=1410) self.pdc[idx].logger.setLevel("INFO") logger.info('PDC initialized') def get_header_config(self): for idx, item in self.pdc.items(): # each item is a PDC item.run() # Connect to PMU self.header[idx] = item.get_header() self.config[idx] = item.get_config() for idx, item in self.pdc.items(): # each item is a PDC item.start() # Request to start sending measurements self.pdc_started = True logger.info('PMU Header and ConfigFrame received') def collect_data(self): pass def process_data(self): pass def run(self): pass
import numpy as np import sys from dime import DimeClient if __name__ != "__main__": raise RuntimeError() d1 = DimeClient("tcp", sys.argv[1], int(sys.argv[2])) d2 = DimeClient("tcp", sys.argv[1], int(sys.argv[2])) d1.join("d1") d2.join("d2") d1["a"] = np.random.rand(500, 500) d1.send("d2", "a") d2.sync() assert np.array_equal(d1["a"], d2["a"])
d = DimeClient("ipc", sys.argv[1]) if int(sys.argv[2]) == 0: d.join("d1") while "d2" not in d.devices(): time.sleep(0.05) d["a"] = "ping" d.send("d2", "a") n = d.wait() assert n == 1 or n == 2 d.sync(1) assert d["a"] == "pong" signal.signal(signal.SIGALRM, timeout) signal.alarm(10) d.wait() signal.alarm(0) else: d.join("d2") while "d1" not in d.devices(): time.sleep(0.05)
import math import numpy as np import sys import time from dime import DimeClient d = DimeClient("ipc", sys.argv[1]); d.join("python"); while "matlab" not in d.devices(): time.sleep(0.05) time.sleep(1) d.sync() assert d["nothing"] is None assert d["boolean"] is True assert d["float"] == math.pi assert d["int"] == 0xDEADBEEF assert d["complexfloat"] == complex(math.sqrt(0.75), 0.5) assert np.array_equal(d["matrix"], np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9.01]])) assert d["string"] == "Hello world!" assert d["sequence"] == [-1, ":)", False, None] assert d["mapping"] == {"foo": 2, "bar": "Green eggs and spam"} d.send("matlab", "nothing", "boolean", "int", "float", "complexfloat", "matrix", "string", "sequence", "mapping");
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() spl = dime_address.split(":") spl[1] = spl[1][2:] if len(spl) == 3: spl[2] = int(spl[2]) self.dimec = DimeClient(*spl) self.dimec.join(self.name) 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 %s', self.dime_address) pass # 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']['Vn'][idx] * 1000 # get Vn logger.info('Retrieved bus Vn %g', 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']) 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 = list(self.dimec.sync(1)) if len(var) == 0: return ret var = var[0] if self.reset is True: logger.info('[%s] variable <%s> synced.', self.name, 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', 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'][self.vgsvaridx] self.t[self.count, :] = data['t'] self.count += 1 # record if self.record_state == RecordState.RECORDING: self.data_record[self.count_record, :] = \ data['vars'][self.vgsvaridx] 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[self.var_idx['vm']] * self.Vn[0] v_ang = wrap_angle(self.last_data[self.var_idx['am']]) v_freq = self.last_data[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)