def implement_setpoint(self, bus_index, P, Q): """Implement a new setpoint. Parameters ---------- bus_index : int Index of the bus to update. P : float New value for the active (P, in W) power. Q : float New value for the reactive (Q, in Var) power. """ sock = socket(AF_INET, SOCK_DGRAM) message = { 'type': 'implement_setpoint', 'bus_index': bus_index, 'P': P, 'Q': Q } data = dump_json_data(message) sock.sendto(data, (self.grid_module_ip, self.grid_module_port))
def send(self): """Send the state of the battery to its RA. """ sock = socket(AF_INET, SOCK_DGRAM) message = { 'SoC_min': self._SoC, 'SoC_max': self._SoC, 'Idc': self._Idc, 'P': self._P, 'Q': self._Q } logger.info("Sending state to RA {}: {}".format( self._reply_addr, message)) data = dump_json_data(message) sock.sendto(data, self._reply_addr)
def reply(addr, state): """Reply to the resource agent with the PV's state. Parameters ---------- addr : tuple Address of the RA. state : dict State of the UCPV. """ message = {'P': state['P'], 'Q': state['Q']} logger.info("Sending setpoint (P = {}, Q = {}) to RA".format( message['P'], message['Q'])) data = dump_json_data(message) sock = socket(AF_INET, SOCK_DGRAM) sock.sendto(data, addr)
def get_state(self, timeout_s=None): """Communicate with the grid module to retrieve the grid's state. If a timeout is specified and is exceeded, the GridAPI will attempt to return the previously known state. If no state was known from history, then the timeout exception is simply re-raised. Parameters ---------- timeout_s : float (optional, default None) Timeout in seconds for the UDP communication. Returns ------- state : dict State of the grid. Raises ------ timeout : socket.timeout Operation timed out and no state was known from history. """ sock = socket(AF_INET, SOCK_DGRAM) sock.settimeout(timeout_s) message = {'type': 'request'} data = dump_json_data(message) try: sock.sendto(data, (self.grid_module_ip, self.grid_module_port)) reply, _ = sock.recvfrom(BUFFER_LIMIT) except timeout as e: try: # Try to return the previous state in the case of a timeout. return self._state except AttributeError: # If impossible, re-raise the timeout exception. raise e self._state = load_json_data(reply) return self._state
def reply(addr, state): """Reply to the resource agent with the Load's state. Parameters ---------- addr : tuple Address of the RA. state : dict State of the Load. """ message = { 'P': -state['P'], # Sign convention in COMMELEC agents is opposite. 'Q': -state['Q'] # Sign convention in COMMELEC agents is opposite. } logger.info("Sending setpoint (P = {}, Q = {}) to RA".format( message['P'], message['Q'])) data = dump_json_data(message) sock = socket(AF_INET, SOCK_DGRAM) sock.sendto(data, addr)
def reply(addr, state, bus_index, msg_format): """Reply to the resource agent with the Load's state. Parameters ---------- addr : tuple Address of the RA. state : dict State of the Load. """ if msg_format == "cpp": measurement_array = [{'P': 0, 'Q': 0} for i in range(bus_index + 1)] measurement_array[bus_index] = { 'P': -state['P'], # Sign convention in COMMELEC agents is opposite. 'Q': -state['Q'] # Sign convention in COMMELEC agents is opposite. } message = {"buses": measurement_array} logger.info( "Sending setpoint (P = {}, Q = {}) to RA (CPP executable format)". format(message["buses"][bus_index]['P'], message["buses"][bus_index]['Q'])) else: message = { 'P': -state['P'], # Sign convention in COMMELEC agents is opposite. 'Q': -state['Q'] # Sign convention in COMMELEC agents is opposite. } logger.info( "Sending setpoint (P = {}, Q = {}) to RA (Labview executable format)" .format(message['P'], message['Q'])) data = dump_json_data(message) sock = socket(AF_INET, SOCK_DGRAM) sock.sendto(data, addr)
def main(): # Parse the arguments. parser = ArgumentParser( description="Grid module that facilitates the operation of the grid.") parser.add_argument("config_path", help="Path to the JSON config file for the grid", nargs='?') parser.add_argument("log_path", help="Path to the log directory for the grid", nargs='?') parser.add_argument("grid_module_ip", help="T-RECS (private) IP of the grid module where \ it listens for the messages from T-RECS resource models.", nargs='?') parser.add_argument("grid_module_port", help="Port where grid module listens for the \ messages from T-RECS resource models.", nargs='?') parser.add_argument("--api_path", help="Path to which the GridAPI will be pickled", default='grid_api.pickle') args = parser.parse_args() # Load the configuration file. config = load_json_file(args.config_path, logger) # Inform the GridAPI of the grid module's address. api = GridAPI(args.grid_module_ip, int(args.grid_module_port)) dump_api(api, args.api_path) kwargs = {'api_path': args.api_path} # Initialize a multiprocessing manager. with Manager() as manager: # Shared memory for the state. state = manager.dict() # Socket to listen for incoming messages. sock = socket(AF_INET, SOCK_DGRAM) sock.bind((args.grid_module_ip, int(args.grid_module_port))) # Handle update messages. message_queue = manager.Queue() state_queue = manager.Queue() Process(target=update_handler, args=(state, message_queue, state_queue, config['grid']), kwargs=kwargs).start() # Log generation. Process(target=log_generator, args=(state_queue, args.log_path)).start() # Wait for the child process to initialize the grid. while not state: continue while True: # The socket listens for messages that ask it to provide its state, # or implement a new setpoint. data, addr = sock.recvfrom(BUFFER_LIMIT) message = load_json_data(data) logger.info("Received message from {}: {}".format(addr, message)) try: if message['type'] == 'request': reply = {key: value for key, value in state.items()} logger.info("Send state to {}: {}".format(addr, reply)) sock.sendto(dump_json_data(reply), addr) elif message['type'] == 'implement_setpoint': logger.info("Implement setpoint: {}".format(message)) message_queue.put((message, datetime.now())) logger.info("Queue size: {}".format(message_queue.qsize())) else: logger.warn("Unknown message type: {}".format( message['type'])) except Exception as e: logger.warn("Bad message: {}".format(e)) return 0
def update(api, bus_indices, default_line_frequency, period, use_trace, trace_path, state_queue): """Update the state of the sensor. Parameters ---------- api : GridAPI API to use to query the grid for the state. bus_indices : iterable Which buses to obtain the state for. default_line_frequency : float default value used for the Frequency of the line. period : float How often to update the information (in seconds). use_trace : boolean Define if the frequency is read from a trace or if a static value (default_line_frequency) is used trace_path : str file path of the frequency trace state_queue : multiprocessing.manager.Queue Queue in which the updated state will be put (for the log). Raises ------ error : IOError Could not open the trace error : ValueError Wrong or missing value in the trace """ # Load trace if use_trace: try: with open(trace_path, 'r') as f: reader_ = reader(f, quoting=QUOTE_NONNUMERIC) slack_line_frequency = list(reader_) except IOError as e: logger.error("Could not open {}: {}".format( slack_line_frequency, e)) return 1 except ValueError as e: logger.error("ValueError, wrong or missing value in {}: {}".format( slack_line_frequency, e)) return 1 except Exception as e: logger.error("Unexpected error", exc_info()[0]) raise # normalize the timestamp of the trace first_freq_ts = slack_line_frequency[0][0] for i in range(0, len(slack_line_frequency)): slack_line_frequency[i][ 0] = slack_line_frequency[i][0] - first_freq_ts found = False end_trace_reach = False ptr_ID = -1 line_frequency = default_line_frequency start_time = default_timer() global state, data bus_indices = frozenset(bus_indices) message = {} while True: try: new_state = api.get_state(period) except timeout as e: logger.warning( "Could not retrieve state from GridAPI: {}".format(e)) continue logger.info("Retrieved state from GridAPI: {}".format(new_state)) if state != new_state: # Update the state. state = new_state.copy() message = {'freq': line_frequency, 'buses': []} for bus_index, (P, Q, Vm, Va) in enumerate( zip(state['P'], state['Q'], state['Vm'], state['Va'])): if bus_index not in bus_indices: continue for phase_index, phase_shift in enumerate((0, 120, -120), 1): phase_angle = radians(Va + phase_shift) message['buses'].append( make_entry(bus_index, phase_index, P / 3, Q / 3, Vm / sqrt(3) * cos(phase_angle), Vm / sqrt(3) * sin(phase_angle))) else: logger.info("State remained unchanged") if use_trace: found = False current_time = default_timer() - start_time delta = abs(current_time - slack_line_frequency[ptr_ID][0]) while (not found and not end_trace_reach): # reach the end of the trace, take the last element as correct value if (ptr_ID + 1) >= len(slack_line_frequency): end_trace_reach = True # take the last entry ptr_ID = -1 logger.info('End of trace reached') else: next_delta = abs(current_time - slack_line_frequency[ptr_ID + 1][0]) # the closest value of current_time is at ptr_ID if next_delta > delta: found = True else: delta = next_delta ptr_ID = ptr_ID + 1 end_l = default_timer() - start_time logger.info('Time spend in nearest algo {}'.format(end_l - current_time)) line_frequency = slack_line_frequency[ptr_ID][1] message['freq'] = line_frequency data = dump_json_data(message) msg_copy = message.copy() msg_copy['Ts'] = datetime.now() state_queue.put(msg_copy) elapsed_time = default_timer() - start_time sleep(period - elapsed_time % period)