diff --git a/simulation_config_files/9500-alarm-config.json b/simulation_config_files/9500-alarm-config.json index 1073e1a..07e2f66 100644 --- a/simulation_config_files/9500-alarm-config.json +++ b/simulation_config_files/9500-alarm-config.json @@ -1,54 +1,133 @@ { - "power_system_config": { - "GeographicalRegion_name": "_73C512BD-7249-4F50-50DA-D93849B89C43", - "SubGeographicalRegion_name": "_A1170111-942A-6ABD-D325-C64886DC4D7D", - "Line_name": "_AAE94E4A-2465-6F5E-37B1-3E72183A4E44" - }, - "application_config": { - "applications": [ - + "power_system_config": { + "GeographicalRegion_name": "_73C512BD-7249-4F50-50DA-D93849B89C43", + "SubGeographicalRegion_name": "_A1170111-942A-6ABD-D325-C64886DC4D7D", + "Line_name": "_AAE94E4A-2465-6F5E-37B1-3E72183A4E44" + }, + "application_config": { + "applications": [] + }, + "simulation_config": { + "start_time": "1373814000", + "duration": "120", + "simulator": "GridLAB-D", + "timestep_frequency": 1000, + "timestep_increment": 1000, + "run_realtime": false, + "simulation_name": "test9500new", + "power_flow_solver_method": "NR", + "model_creation_config": { + "load_scaling_factor": 1, + "schedule_name": "ieeezipload", + "z_fraction": 0, + "i_fraction": 1, + "p_fraction": 0, + "randomize_zipload_fractions": false, + "use_houses": false + } + }, + "test_config": { + "events": [ + { + "message": { + "forward_differences": [ + { + "object": "_792127B0-9B3E-43EC-9D23-FD46F5A2F20D", + "attribute": "Switch.open", + "value": 1 + } + ], + "reverse_differences": [ + { + "object": "_792127B0-9B3E-43EC-9D23-FD46F5A2F20D", + "attribute": "Switch.open", + "value": 0 + } + ] + }, + "event_type": "ScheduledCommandEvent", + "occuredDateTime": 1373814030, + "stopDateTime": 1373814045 + }, + { + "message": { + "forward_differences": [ + { + "object": "_28CFA3B0-5D09-4154-BC5A-BD0C45E9530D", + "attribute": "Switch.open", + "value": 1 + } + ], + "reverse_differences": [ + { + "object": "_28CFA3B0-5D09-4154-BC5A-BD0C45E9530D", + "attribute": "Switch.open", + "value": 0 + } + ] + }, + "event_type": "ScheduledCommandEvent", + "occuredDateTime": 1373814040, + "stopDateTime": 1373814055 + }, + { + "message": { + "forward_differences": [ + { + "object": "_B2C95152-D6F7-4702-9910-E2543FB9B4F7", + "attribute": "Switch.open", + "value": 1 + } + ], + "reverse_differences": [ + { + "object": "_B2C95152-D6F7-4702-9910-E2543FB9B4F7", + "attribute": "Switch.open", + "value": 0 + } + ] + }, + "event_type": "ScheduledCommandEvent", + "occuredDateTime": 1373814040, + "stopDateTime": 1373814065 + }, + { + "message": { + "forward_differences": [ + { + "object": "_5D67EBEE-9158-4C56-9E39-12A9AC0D7B8D", + "attribute": "TapChanger.step", + "value": 10 + } + ], + "reverse_differences": [ + { + "object": "_5D67EBEE-9158-4C56-9E39-12A9AC0D7B8D", + "attribute": "TapChanger.step", + "value": 5 + } + ] + }, + "event_type": "ScheduledCommandEvent", + "occuredDateTime": 1373814050, + "stopDateTime": 1373814100 + } + ], + "appId": "" + }, + "service_configs": [ + { + "id": "gridappsd-alarms", + "user_options": { + "default-perunit-confidence-band": 0.02, + "simulate-all": false, + "sensors-config": {}, + "default-normal-value": 100, + "random-seed": 0, + "default-aggregation-interval": 30, + "passthrough-if-not-specified": false, + "default-perunit-drop-rate": 0.05 + } + } ] - }, - "simulation_config": { - "start_time": "1373814000", - "duration": "1200", - "simulator": "GridLAB-D", - "timestep_frequency": 1000, - "timestep_increment": 1000, - "run_realtime": false, - "simulation_name": "test9500new", - "power_flow_solver_method": "NR", - "model_creation_config": { - "load_scaling_factor": 1, - "schedule_name": "ieeezipload", - "z_fraction": 0, - "i_fraction": 1, - "p_fraction": 0, - "randomize_zipload_fractions": false, - "use_houses": false - } - }, - - "test_config": { - "events":[{"message":{"forward_differences":[{"object":"_302E3119-B3ED-46A1-87D5-EBC8496357DF","attribute":"Switch.open","value":1}],"reverse_differences":[{"object":"_302E3119-B3ED-46A1-87D5-EBC8496357DF","attribute":"Switch.open","value":0}]},"event_type":"ScheduledCommandEvent","occuredDateTime":1373814120,"stopDateTime":1373817600} , - {"message":{"forward_differences": [{"object": "_A0E0AB93-FFC2-471B-B84C-19015CB15ED2","attribute": "Switch.open","value": 1}],"reverse_differences": [{"object": "_A0E0AB93-FFC2-471B-B84C-19015CB15ED2","attribute": "Switch.open","value": 0}]}, "event_type": "ScheduledCommandEvent","occuredDateTime": 1373814125,"stopDateTime": 1373817600}, - {"message": {"forward_differences": [{"object": "_2FA4B41B-C31B-4861-B8BB-941A8DFD1B41","attribute": "Switch.open","value": 1}],"reverse_differences": [{"object": "_2FA4B41B-C31B-4861-B8BB-941A8DFD1B41","attribute": "Switch.open","value": 0}]}, "event_type": "ScheduledCommandEvent","occuredDateTime": 1373814125,"stopDateTime": 1373817600}, - {"message": {"forward_differences": [{"object": "_E44571D4-52CE-4ACE-9012-37DEBF17FCF8","attribute": "TapChanger.step", "value": 10}],"reverse_differences": [{"object": "_E44571D4-52CE-4ACE-9012-37DEBF17FCF8","attribute":"TapChanger.step","value": 5}]},"event_type": "ScheduledCommandEvent","occuredDateTime": 1373814600,"stopDateTime": 1373817600}, - {"allOutputOutage": false,"allInputOutage": false,"tag": "mc45mjk2","inputList": [{"name": "vreg3_a","type": "Regulator","mRID": ["_E44571D4-52CE-4ACE-9012-37DEBF17FCF8"],"attribute": "TapChanger.step","phases": [{"phaseLabel": "A","phaseIndex": 0}]}],"outputList": [],"event_type": "CommOutage","startDateTime": 1373814610,"stopDateTime": 1373817600}], - "appId": "" - }, - "service_configs": [{ - "id": "gridappsd-alarms", - "user_options": { - "default-perunit-confidence-band": 0.02, - "simulate-all": false, - "sensors-config": {}, - "default-normal-value": 100, - "random-seed": 0, - "default-aggregation-interval": 30, - "passthrough-if-not-specified": false, - "default-perunit-drop-rate": 0.05 - } -}] } - diff --git a/simulation_config_files/config.json b/simulation_config_files/config.json deleted file mode 100644 index e69de29..0000000 diff --git a/test_alarm_api.py b/test_alarm_api.py index 1d44215..0cb6fd2 100644 --- a/test_alarm_api.py +++ b/test_alarm_api.py @@ -2,173 +2,105 @@ import json import logging import os +import yaml from time import sleep, time import sys -import pandas as pd import pytest -from numbers import Number -from math import isclose -import yaml -from gridappsd import GridAPPSD, topics as t -# tm: added for run_simulation workaround +from gridappsd import GridAPPSD from gridappsd.simulation import Simulation from gridappsd_docker import docker_up, docker_down +from gridappsd import topics as t LOGGER = logging.getLogger(__name__) -logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) - - -@contextmanager -def startup_containers(spec=None): - LOGGER.info('Starting gridappsd containers') - docker_up(spec) - LOGGER.info('Containers started') - - yield - - LOGGER.info('Stopping gridappsd containers') - docker_down() - LOGGER.info('Containers stopped') - -@contextmanager -def gappsd() -> GridAPPSD: - gridappsd = GridAPPSD() - LOGGER.info('Gridappsd connected') - - yield gridappsd - - gridappsd.disconnect() - LOGGER.info('Gridappsd disconnected') - - -# def test_start_gridappsd(): -# with startup_containers(): -# g = GridAPPSD() -# assert g.connected -# global tapchanger_value - -tapchanger_value = -1 +tapchanger_value = [] alarm_count = 0 -outage_mrid = "" -count1 = 0 -count2 = 0 - def on_message(headers, message): global tapchanger_value global alarm_count - global outage_mrid - global count1 - global count2 + if "gridappsd-alarms" in headers["destination"]: - # print(headers) - if "_302E3119-B3ED-46A1-87D5-EBC8496357DF" or "_A0E0AB93-FFC2-471B-B84C-19015CB15ED2" or "_2FA4B41B-C31B-4861-B8BB-941A8DFD1B41" in \ - message['equipment_mrid']: + if "ln1047pvfrm_sw" or "ln5001chp_sw" or "ln0895780_sw" in \ + message['equipment_name']: for y in message: - print(y) if "Open" in y['value']: - LOGGER.info('Alarms created') + LOGGER.info(f'Alarm created {y}') alarm_count += 1 if "gridappsd-alarms" not in headers["destination"]: - # print(headers) measurement_values = message["message"]["measurements"] for x in measurement_values: - # print(measurement_values) m = measurement_values[x] - if m.get("measurement_mrid") == "_0f8202ca-a4bf-4c7e-9302-601919c09992": - if m.get("value") != tapchanger_value: - tapchanger_value = m.get("value") - - LOGGER.info(f"Tap Changer Value changed to {tapchanger_value}") - count2 += 1 + if m.get("measurement_mrid") == "_48e11ee1-ea9f-4e0c-a6dd-2807a9dbc032": + if not tapchanger_value: + LOGGER.info(f'Tap Changer value is {m.get("value")}') + tapchanger_value.append(m.get("value")) + else: + if m.get("value") != tapchanger_value[-1]: + LOGGER.info(f'Tap Changer value changed from {tapchanger_value[-1]} to {m.get("value")} {m}') + tapchanger_value.append(m.get("value")) +@pytest.mark.parametrize("sim_config_file, sim_result_file", [ + ("9500-alarm-config.json", "9500-alarm-simulation.output") - if m.get("measurement_mrid") == "_4b707748-2846-4517-92f6-fa6c3fbdd1ed": - count1 += 1 +]) +def test_alarm_output(gridappsd_client, 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" - elif m.get("measurement_mrid") == outage_mrid: - outage_mrid = "_0f8202ca-a4bf-4c7e-9302-601919c09992" - print("mRID not present") + gapps = gridappsd_client + # Allow proven to come up + sleep(30) + + sim_complete = False + rcvd_measurement = False - # if "_0f8202ca-a4bf-4c7e-9302-601919c09992" not in m.get("measurement_mrid"): - # print("mRID not there") - # with open("./input.txt", 'w') as f: - # f.write(x) + + #def onmeasurement(sim, timestep, measurements): + # nonlocal rcvd_measurement + # if not rcvd_measurement: + # rcvd_measurement = True + # LOGGER.info('A measurement happened at %s', timestep) - # if m.get("measurement_mrid") == "_E44571D4-52CE-4ACE-9012-37DEBF17FCF8": - # print("mRID not there") - # with open("./input.txt", 'a') as f: - # f.write(x) + def onfinishsimulation(sim): + nonlocal sim_complete + sim_complete = True + LOGGER.info('Simulation Complete') + with open(sim_config_file) as fp: + LOGGER.info('Loading config') + run_config = json.load(fp) + LOGGER.info(f'Simulation start time {run_config["simulation_config"]["start_time"]}') -@pytest.mark.parametrize("sim_config_file, sim_result_file", [ - ("9500-alarm-config.json", "9500-alarm-simulation.output") - -]) -def test_alarm_output(sim_config_file, sim_result_file): + sim = Simulation(gapps, run_config) - simulation_id = None - 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}") + LOGGER.info('Starting the simulation') + sim.start_simulation() - assert os.path.exists(sim_config_file), f"File {sim_config_file} must exist to run simulation test" + #LOGGER.info('sim.add_onmesurement_callback') + #sim.add_onmesurement_callback(onmeasurement) + LOGGER.info('sim.add_oncomplete_callback') + sim.add_oncomplete_callback(onfinishsimulation) - with startup_containers(): - with gappsd() as gapps: - os.makedirs("/tmp/output", exist_ok=True) - with open("/tmp/output/simulation.output", 'w') as outfile: - sim_complete = False - rcvd_measurement = False - - def onmeasurement(sim, timestep, measurements): - nonlocal rcvd_measurement - # if not rcvd_measurement: - rcvd_measurement = True - LOGGER.info('A measurement happened at %s', timestep) - - def onfinishsimulation(sim): - nonlocal sim_complete - sim_complete = True - print("Completed simulator") - - with open(sim_config_file) as fp: - run_config = json.load(fp) - print(run_config["simulation_config"]["start_time"]) - - sim = Simulation(gapps, run_config) - - LOGGER.info('Starting the simulation') - sim.add_onmesurement_callback(onmeasurement) - sim.add_oncomplete_callback(onfinishsimulation) - LOGGER.info('sim.add_onmesurement_callback') - LOGGER.info("Querying Alarm topic for alarms") - sim.start_simulation() - print(sim.simulation_id) - alarms_topic = t.service_output_topic('gridappsd-alarms', sim.simulation_id) - log_topic = t.simulation_output_topic(sim.simulation_id) - input_topic = t.simulation_input_topic(sim.simulation_id) - gapps.subscribe(alarms_topic, on_message) - gapps.subscribe(log_topic, on_message) - # gapps.subscribe(input_topic, on_message) - - while not sim_complete: - LOGGER.info('Sleeping') - sleep(30) + LOGGER.info("Querying for alarm topic") + alarms_topic = t.service_output_topic('gridappsd-alarms', sim.simulation_id) + log_topic = t.simulation_output_topic(sim.simulation_id) + gapps.subscribe(alarms_topic, on_message) + gapps.subscribe(log_topic, on_message) + while not sim_complete: + LOGGER.info('Sleeping') + sleep(30) def test_tap_changer(): global tapchanger_value - global count1 - global count2 - print('A', count2) - print('B', count1) - assert tapchanger_value == 10, "Tap Changer value is not as expected" + assert tapchanger_value == [4, 10, 5], f"Expected tap changer values [4, 10, 5] received {tapchanger_value}" def test_alarm_count(): global alarm_count - assert alarm_count == 3, "Three Alarms were not generated" + assert alarm_count == 3, f"Expecting 3 alarms received {alarm_count}" diff --git a/test_power_api.py b/test_power_api.py index 3ea95f8..ab12b09 100644 --- a/test_power_api.py +++ b/test_power_api.py @@ -5,40 +5,17 @@ from time import sleep import pytest +from gridappsd import GridAPPSD from gridappsd import GridAPPSD,topics as t from gridappsd.simulation import Simulation from gridappsd_docker import docker_up, docker_down -# from gridappsd_alarms import SimulationSubscriber + LOGGER = logging.getLogger(__name__) POWERGRID_MODEL = 'powergridmodel' database_type = POWERGRID_MODEL request_topic = '.'.join((t.REQUEST_DATA, database_type)) -@contextmanager -def startup_containers(spec=None): - LOGGER.info('Starting gridappsd containers') - docker_up(spec) - LOGGER.info('Containers started') - - yield - - LOGGER.info('Stopping gridappsd containers') - docker_down() - LOGGER.info('Containers stopped') - - -@contextmanager -def gappsd() -> GridAPPSD: - gridappsd = GridAPPSD() - LOGGER.info('Gridappsd connected') - - yield gridappsd - - gridappsd.disconnect() - LOGGER.info('Gridappsd disconnected') - - def model_files_are_equal(file1, file2): with open(file1, 'r') as f1: with open(file2, 'r') as f2: @@ -145,35 +122,37 @@ def query_data_equal(file1, file2): return True -def test_power_model_names(): - with startup_containers(): - with gappsd() as gapps: - LOGGER.info('Performing model name query') - with open("/tmp/output/power.json", 'w') as f: - r = gapps.query_model_names(model_id=None) - f.write(json.dumps(r, indent=4, sort_keys=True)) - - LOGGER.info('Performing object query') - with open("/tmp/output/power2.json", 'w') as f: - obj = '_46EA069B-F08C-4945-9C08-8F7CABECCF5C' - r2 = gapps.query_object(obj, model_id=None) - f.write(json.dumps(r2, indent=4, sort_keys=True)) - - LOGGER.info('Performing object type query') - with open("/tmp/output/power3.json", 'w') as f: - r3 = gapps.query_object_types(model_id=None) - f.write(json.dumps(r3, indent=4, sort_keys=True)) - - LOGGER.info('Performing model info query') - with open("/tmp/output/power4.json", 'w') as f: - r4 = gapps.query_model_info() - f.write(json.dumps(r4, indent=4, sort_keys=True)) - - LOGGER.info('Performing model data query') - with open("/tmp/output/power5.json", 'w') as f: - query = "select ?feeder_name ?subregion_name ?region_name WHERE {?line r:type c:Feeder.?line c:IdentifiedObject.name ?feeder_name.?line c:Feeder.NormalEnergizingSubstation ?substation.?substation r:type c:Substation.?substation c:Substation.Region ?subregion.?subregion c:IdentifiedObject.name ?subregion_name .?subregion c:SubGeographicalRegion.Region ?region . ?region c:IdentifiedObject.name ?region_name}" - r5 = gapps.query_data(query, database_type=POWERGRID_MODEL, timeout=30) - f.write(json.dumps(r5, indent=4, sort_keys=True)) +def test_power_model_names(gridappsd_client): + gapps = gridappsd_client + # Allow blazegraph to come up + sleep(30) + os.makedirs("/tmp/output", exist_ok=True) + LOGGER.info('Performing model name query') + with open("/tmp/output/power.json", 'w') as f: + r = gapps.query_model_names(model_id=None) + f.write(json.dumps(r, indent=4, sort_keys=True)) + + LOGGER.info('Performing object query') + with open("/tmp/output/power2.json", 'w') as f: + obj = '_46EA069B-F08C-4945-9C08-8F7CABECCF5C' + r2 = gapps.query_object(obj, model_id=None) + f.write(json.dumps(r2, indent=4, sort_keys=True)) + + LOGGER.info('Performing object type query') + with open("/tmp/output/power3.json", 'w') as f: + r3 = gapps.query_object_types(model_id=None) + f.write(json.dumps(r3, indent=4, sort_keys=True)) + + LOGGER.info('Performing model info query') + with open("/tmp/output/power4.json", 'w') as f: + r4 = gapps.query_model_info() + f.write(json.dumps(r4, indent=4, sort_keys=True)) + + LOGGER.info('Performing model data query') + with open("/tmp/output/power5.json", 'w') as f: + query = "select ?feeder_name ?subregion_name ?region_name WHERE {?line r:type c:Feeder.?line c:IdentifiedObject.name ?feeder_name.?line c:Feeder.NormalEnergizingSubstation ?substation.?substation r:type c:Substation.?substation c:Substation.Region ?subregion.?subregion c:IdentifiedObject.name ?subregion_name .?subregion c:SubGeographicalRegion.Region ?region . ?region c:IdentifiedObject.name ?region_name}" + r5 = gapps.query_data(query, database_type=POWERGRID_MODEL, timeout=30) + f.write(json.dumps(r5, indent=4, sort_keys=True)) assert model_files_are_equal('/tmp/output/power.json', './simulation_baseline_files/power_api_models.json'), 'Powergrid API model name differs' diff --git a/test_timeseries.py b/test_timeseries.py index 4a85d31..a8b9c85 100644 --- a/test_timeseries.py +++ b/test_timeseries.py @@ -41,7 +41,7 @@ def gappsd() -> GridAPPSD: @pytest.mark.parametrize("sim_config_file, sim_result_file", [ - ("config.json", "9500-simulation.json") + ("9500-timeseries-config.json", "9500-simulation.json") # ("123-config.json", "123-simulation.json"), # ("13-node-config.json", "13-node-sim.json"), # , ("t3-p1-config.json", "t3-p1.json"), @@ -117,4 +117,4 @@ def onfinishsimulation(sim): LOGGER.info('Querying GridAPPS-D sensor simulator data from timeseries') result = gapps.get_response(t.TIMESERIES, sensor_query, timeout=300) assert "hasSimulationMessageType" in result["data"][0], "Sensor simulator data does not have expected output" - LOGGER.info('Query response received for GridAPPS-D sensor simulator data from timeseries') \ No newline at end of file + LOGGER.info('Query response received for GridAPPS-D sensor simulator data from timeseries')