class GridAPPSDSimIntegration(BaseSimIntegration): """ The class is responsible for integration with GridAPPSD co-simulation platform. It provides integration support to register configuration, start, stop, publish, receive messages, pause and resume simulation """ def __init__(self, config, pubsub): super(GridAPPSDSimIntegration, self).__init__(config) self._work_callback = None self.config = config self.gridappsd = None self.sim = None self.event_callbacks = {} self.topic_callbacks = {} self.sim_id = None self.username = None self.password = None def register_inputs(self, config=None, callback=None, **kwargs): """ Register configuration parameters with GridAppsD. The config parameters may include but not limited to: - power_system_config - application_config - simulation_config - test_config - service_configs : Register agent callback method :return: """ self.config = config self.username = self.config.pop('username', 'system') self.password = self.config.pop('password', 'manager') self._work_callback = callback def register_event_callbacks(self, callbacks={}): """ Register for event callbacks for event notifications such as - on measurement change - on timestep change - on finish """ _log.debug("Registering for event callbacks") self.event_callbacks = callbacks def register_topic_callbacks(self, callbacks={}): """ Register for any simulation topic callbacks """ _log.debug("Registering for topic callbacks") self.topic_callbacks = callbacks def start_simulation(self, *args, **kwargs): """ Simulation start activities involve: - Creating GridAppsD connection gevent thread - Registering for event callbacks (if specified) - Registering for topic callbacks if specified - Starting simulation based on the input config :return: """ try: self.gridappsd = GridAPPSD(override_threading=self.receiver_thread, username=self.username, password=self.password) _log.debug('Gridappsd connected') _log.debug(f"connection config is: {self.config}") self.sim = Simulation(self.gridappsd, self.config) _log.debug('Gridappsd adding onstart callback') # Register for onstart callback to know if simulation has started self.sim.add_onstart_callback(self.sim_on_start) # Register event callbacks - on measurement, on timestep, on finish for name, cb in self.event_callbacks.items(): if name == 'MEASUREMENT': _log.debug('Gridappsd adding measurement callback') self.sim.add_onmesurement_callback(cb) elif name == 'TIMESTEP': _log.debug('Gridappsd adding timestep callback') self.sim.add_ontimestep_callback(cb) elif name == 'FINISH': _log.debug('Gridappsd adding finish callback') self.sim.add_oncomplete_callback(cb) # Register/Subscribe for simulation topics for topic, cb in self.topic_callbacks: _log.debug('Gridappsd subscribing to topics callback') self.gridappsd.subscribe(topic, cb) # Starting GridAppsD simulation self.sim.start_simulation() _log.debug(f"Gridappsd simulation id: {self.sim.simulation_id}") except stomp.exception.NotConnectedException as ex: _log.error("Unable to connect to GridAPPSD: {}".format(ex)) raise ex def sim_on_start(self, sim): """ Simulation on start callback to get notified when simulation starts """ _log.debug( f"GridAppsD simulation id inside sim_on_start(): {sim.simulation_id}" ) self.sim_id = sim.simulation_id def receiver_thread(self, arg): """ GridAPPSD connection thread """ self._receiver_thread = gevent.threading.Thread(group=None, target=arg) self._receiver_thread.daemon = True # Don't let thread prevent termination self._receiver_thread.start() _log.debug('Gridappsd receiver_thread started!') return self._receiver_thread def publish_to_simulation(self, topic, message, **kwargs): """ Publish message to GridAppsD :param topic: GridAppsD publication topic :param message: message :return: """ self.gridappsd.send(topic, message) def pause_simulation(self, timeout=None, **kwargs): """ Pause the GridAppsD simulation """ if timeout is None: self.sim.pause() else: self.sim.pause(timeout) def resume_simulation(self, *args, **kwargs): """ Resume the GridAppsD simulation """ self.sim.resume() def is_sim_installed(self, **kwargs): """ Flag to indicate if GridAppsD is installed """ return HAS_GAPPSD def stop_simulation(self, *args, **kwargs): """ Stop the simulation if running and disconnect from GridAppsD server :return: """ _log.debug('Stopping the simulation') try: if self.sim_id is not None: self.sim.stop() _log.debug('Disconnect GridAppsd') if self.gridappsd is not None: self.gridappsd.disconnect() except Exception: _log.error("Error stop GridAPPSD simulation")
def test_simulation_output(sim_config_file, sim_result_file): sim_config_file = os.path.join( os.path.dirname(__file__), f"simulation_config_files/{sim_config_file}") sim_result_file = os.path.join( os.path.dirname(__file__), f"simulation_baseline_files/{sim_result_file}") assert os.path.exists( sim_config_file ), f"File {sim_config_file} must exist to run simulation test" # assert os.path.exists(sim_result_file), f"File {sim_result_file} must exist to run simulation test" with startup_containers(): # Allow proven to come up sleep(30) starttime = int(time()) with gappsd() as gapps: os.makedirs("/tmp/output", exist_ok=True) with open("/tmp/output/simulation.output", 'w') as outfile: LOGGER.info('Configuring simulation') sim_complete = False rcvd_measurement = False rcvd_first_measurement = 0 are_we_paused = False with open(sim_config_file) as fp: LOGGER.info('Reading config') run_config = json.load(fp) run_config["simulation_config"]["start_time"] = str( starttime) sim = Simulation(gapps, run_config) def onmeasurement(sim, timestep, measurements): LOGGER.info('Measurement received at %s', timestep) nonlocal rcvd_measurement nonlocal rcvd_first_measurement nonlocal are_we_paused if not are_we_paused and not rcvd_first_measurement: LOGGER.debug("Pausing sim now") sim.pause() are_we_paused = True LOGGER.debug(f"ARWEPAUSED {are_we_paused}") # Setting up so if we get another measurement wheil we # are paused we know it rcvd_measurement = False if not rcvd_measurement: print(f"A measurement happened at {timestep}") # outfile.write(f"{timestep}|{json.dumps(measurements)}\n") data = {"data": measurements} outfile.write(json.dumps(data)) rcvd_measurement = True else: rcvd_measurement = True rcvd_first_measurement = True # sleep here until rcvd_measuremnt = True again def ontimestep(sim, timestep): print("Timestamp: {}".format(timestep)) def onfinishsimulation(sim): nonlocal sim_complete sim_complete = True LOGGER.info('Simulation Complete') LOGGER.info(f"Start time is {starttime}") LOGGER.info('Loading config') # tm: typo in add_onmesurement LOGGER.info('sim.add_onmesurement_callback') LOGGER.info('Starting sim') sim.start_simulation() print(sim.simulation_id) sim.add_onmesurement_callback(onmeasurement) sim.add_ontimestep_callback(ontimestep) gapps.subscribe(t.simulation_log_topic(sim.simulation_id), on_message) sim.add_oncomplete_callback(onfinishsimulation) LOGGER.info('sim.add_oncomplete_callback') secs = 0 while secs < 30: LOGGER.info(f"Sleep {secs}") # we have to wait until the first measurement is called before # the are_we_paused could have a chance of being set secs += 1 sleep(1) paused_seconds = 0 while are_we_paused: LOGGER.info(f"PAUSED {paused_seconds}") paused_seconds += 1 # s if paused_seconds > 30: LOGGER.info('Resuming simulation') sim.resume() LOGGER.info('Resumed simulation') are_we_paused = False break sleep(1) assert not are_we_paused, "We should have came out of the paused_seconds > 30" while not sim_complete: LOGGER.info('Sleeping') sleep(5)
# from pprint import pprint # pprint(config['gridappsd']) with run_containers(config, stop_after=True) as containers: # Watches the log on the container for the MYSQL data. containers.wait_for_log_pattern("gridappsd", "MYSQL") gappsd = GridAPPSD() gappsd.connect() assert gappsd.connected time.sleep(10) sim = Simulation(gappsd, run_config=run_config) sim.add_onstart_callback(onstart) sim.add_oncomplete_callback(onfinish) sim.add_ontimestep_callback(ontimestep) sim.add_onmesurement_callback(onmeasurement) sim.start_simulation() sim.pause() gappsd.subscribe(t.simulation_output_topic(sim.simulation_id), onsimulationoutput) gappsd.subscribe(t.application_output_topic(GRIDAPPSD_SERVICE_ID, sim.simulation_id), onsensoroutput) time.sleep(10) sim.resume() sim.run_loop() print("Shutting down") fh_sim_measurement.close() fh_sensor_measurement.close() fncs_measurements.close()