diff --git a/LocalFeeder/Dockerfile b/LocalFeeder/Dockerfile new file mode 100644 index 0000000..8a2427b --- /dev/null +++ b/LocalFeeder/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.10.6-slim-bullseye +RUN apt-get update +RUN apt-get install -y git ssh +RUN mkdir LocalFeeder +COPY . ./LocalFeeder +WORKDIR ./LocalFeeder +RUN pip install -r requirements.txt +EXPOSE 5678/tcp +CMD ["python", "server.py"] diff --git a/LocalFeeder/requirements.txt b/LocalFeeder/requirements.txt index 0f9e327..2a244ea 100644 --- a/LocalFeeder/requirements.txt +++ b/LocalFeeder/requirements.txt @@ -11,5 +11,5 @@ boto3 xarray fastapi uvicorn -oedisi==1.2.1 +oedisi python-multipart \ No newline at end of file diff --git a/LocalFeeder/server.py b/LocalFeeder/server.py index bb609f7..85032fc 100644 --- a/LocalFeeder/server.py +++ b/LocalFeeder/server.py @@ -1,19 +1,21 @@ +from fastapi import FastAPI, BackgroundTasks, UploadFile, Request +from fastapi.exceptions import HTTPException +from fastapi.responses import JSONResponse +from sender_cosim import run_simulator +import traceback import asyncio -import json import logging -import os +import zipfile +import uvicorn import socket -import sys +import json import time -import traceback -import zipfile +import sys +import os -import uvicorn -from fastapi import BackgroundTasks, FastAPI, Request, UploadFile -from fastapi.exceptions import HTTPException -from fastapi.responses import JSONResponse -from oedisi.types.common import BrokerConfig, HeathCheck, ServerReply -from sender_cosim import run_simulator +from oedisi.componentframework.system_configuration import ComponentStruct +from oedisi.types.common import ServerReply, HeathCheck, DefaultFileNames +from oedisi.types.common import BrokerConfig REQUEST_TIMEOUT_SEC = 1200 @@ -49,7 +51,7 @@ def read_root(): return JSONResponse(response, 200) -@app.get("/sensor/") +@app.get("/sensor") async def sensor(): logging.info(os.getcwd()) sensor_path = os.path.join(base_path, "sensors", "sensors.json") @@ -61,7 +63,7 @@ async def sensor(): return data -@app.post("/profiles/") +@app.post("/profiles") async def upload_profiles(file: UploadFile): try: data = file.file.read() @@ -94,7 +96,7 @@ async def upload_profiles(file: UploadFile): ) -@app.post("/model/") +@app.post("/model") async def upload_model(file: UploadFile): try: data = file.file.read() @@ -123,7 +125,7 @@ async def upload_model(file: UploadFile): HTTPException(500, "Unknown error while uploading userdefined opendss model.") -@app.post("/run/") +@app.post("/run") async def run_feeder( broker_config: BrokerConfig, background_tasks: BackgroundTasks ): # :BrokerConfig @@ -138,6 +140,20 @@ async def run_feeder( HTTPException(500, str(err)) +@app.post("/configure") +async def configure(component_struct:ComponentStruct): + component = component_struct.component + params = component.parameters + params["name"] = component.name + links = {} + for link in component_struct.links: + links[link.target_port] = f"{link.source}/{link.source_port}" + json.dump(links , open(DefaultFileNames.INPUT_MAPPING.value, "w")) + json.dump(params , open(DefaultFileNames.STATIC_INPUTS.value, "w")) + response = ServerReply( + detail = f"Sucessfully updated configuration files." + ).dict() + return JSONResponse(response, 200) + if __name__ == "__main__": - port = int(sys.argv[2]) - uvicorn.run(app, host="0.0.0.0", port=port) + uvicorn.run(app, host="0.0.0.0", port=int(os.environ['PORT'])) diff --git a/broker/Dockerfile b/broker/Dockerfile index b6ce2a9..0c009d8 100644 --- a/broker/Dockerfile +++ b/broker/Dockerfile @@ -12,4 +12,4 @@ RUN pip install -r requirements.txt EXPOSE 8766/tcp -CMD ["python", "server.py", "10.5.0.2", "8766"] +CMD ["python", "server.py"] diff --git a/broker/docker-compose.yml b/broker/docker-compose.yml new file mode 100644 index 0000000..791e0bb --- /dev/null +++ b/broker/docker-compose.yml @@ -0,0 +1,19 @@ +networks: + custom-network: + driver: bridge + ipam: + config: + - gateway: 10.5.0.1 + subnet: 10.5.0.0/16 +services: + oedisi_broker: + build: + context: ./broker/. + environment: + PORT: '8766' + hostname: broker + image: aadillatif/oedisi_broker + networks: + custom-network: {} + ports: + - 8766:8766 diff --git a/broker/requirements.txt b/broker/requirements.txt index d887837..ef7ac5e 100644 --- a/broker/requirements.txt +++ b/broker/requirements.txt @@ -3,6 +3,6 @@ helics-apps==3.4.0 pyyaml fastapi uvicorn -oedisi==1.2.1 +oedisi grequests python-multipart diff --git a/broker/server.py b/broker/server.py index ec5cc03..662e7b5 100644 --- a/broker/server.py +++ b/broker/server.py @@ -1,47 +1,63 @@ -import logging -import os -import shutil -import socket -import sys -import time -import traceback -import zipfile - -import grequests +from fastapi import FastAPI, BackgroundTasks, UploadFile +from fastapi.responses import FileResponse, JSONResponse +from fastapi.exceptions import HTTPException import helics as h +import grequests +import traceback import requests +import zipfile import uvicorn +import logging +import socket +import time import yaml -from fastapi import BackgroundTasks, FastAPI, UploadFile -from fastapi.exceptions import HTTPException -from fastapi.responses import FileResponse, JSONResponse -from oedisi.types.common import HeathCheck, ServerReply +import json +import os +import json + +from oedisi.componentframework.system_configuration import WiringDiagram, ComponentStruct +from oedisi.types.common import ServerReply, HeathCheck from oedisi.tools.broker_utils import get_time_data +logger = logging.getLogger('uvicorn.error') + app = FastAPI() +is_kubernetes_env = os.environ['KUBERNETES_SERVICE_NAME'] if 'KUBERNETES_SERVICE_NAME' in os.environ else None +WIRING_DIAGRAM_FILENAME = "system.json" +WIRING_DIAGRAM : WiringDiagram | None = None + +def build_url(host:str, port:int, enpoint:list): + if is_kubernetes_env: + KUBERNETES_SERVICE_NAME = os.environ['KUBERNETES_SERVICE_NAME'] + url = f"http://{host}.{KUBERNETES_SERVICE_NAME}:{port}/" + else: + url = f"http://{host}:{port}/" + url = url + "/".join(enpoint) + "/" + return url + def find_filenames(path_to_dir=os.getcwd(), suffix=".feather"): filenames = os.listdir(path_to_dir) return [filename for filename in filenames if filename.endswith(suffix)] def read_settings(): - component_map = {} - with open("docker-compose.yml", "r") as stream: - config = yaml.safe_load(stream) - services = config["services"] - print(services) - broker = services.pop("oedisi_broker") - broker_ip = broker["networks"]["custom-network"]["ipv4_address"] - api_port = int(broker["ports"][0].split(":")[0]) - - for service in services: - ip = services[service]["networks"]["custom-network"]["ipv4_address"] - port = int(services[service]["ports"][0].split(":")[0]) - component_map[ip] = port + + broker_host = socket.gethostname() + broker_ip = socket.gethostbyname(broker_host) + api_port = 8766 #int(os.environ['PORT']) + + component_map = { + broker_host: api_port + } + if WIRING_DIAGRAM: + for component in WIRING_DIAGRAM.components: + component_map[component.host] = component.container_port + else: + logger.info("Use the '/configure' setpoint to setup up the WiringDiagram before making requests other enpoints") - return services, component_map, broker_ip, api_port + return component_map, broker_ip, api_port @app.get("/") @@ -53,15 +69,14 @@ def read_root(): return JSONResponse(response, 200) - -@app.post("/profiles/") +@app.post("/profiles") async def upload_profiles(file: UploadFile): try: - services, _, _, _ = read_settings() - for service in services: - if "feeder" in service.lower(): - ip = services[service]["networks"]["custom-network"]["ipv4_address"] - port = int(services[service]["ports"][0].split(":")[0]) + component_map, _, _ = read_settings() + for hostname in component_map: + if "feeder" in hostname: + ip = hostname + port = component_map[hostname] data = file.file.read() if not file.filename.endswith(".zip"): HTTPException( @@ -69,7 +84,10 @@ async def upload_profiles(file: UploadFile): ) with open(file.filename, "wb") as f: f.write(data) - url = f"http://{ip}:{port}/profiles/" + + url = build_url(ip, port, ["profiles"]) + logger.info(f"making a request to url - {url}") + files = {"file": open(file.filename, "rb")} r = requests.post(url, files=files) response = ServerReply(detail=r.text).dict() @@ -79,15 +97,14 @@ async def upload_profiles(file: UploadFile): err = traceback.format_exc() raise HTTPException(status_code=500, detail=str(err)) - -@app.post("/model/") +@app.post("/model") async def upload_model(file: UploadFile): try: - services, _, _, _ = read_settings() - for service in services: - if "feeder" in service.lower(): - ip = services[service]["networks"]["custom-network"]["ipv4_address"] - port = int(services[service]["ports"][0].split(":")[0]) + component_map, _, _ = read_settings() + for hostname in component_map: + if "feeder" in hostname: + ip = hostname + port = component_map[hostname] data = file.file.read() if not file.filename.endswith(".zip"): HTTPException( @@ -95,7 +112,10 @@ async def upload_model(file: UploadFile): ) with open(file.filename, "wb") as f: f.write(data) - url = f"http://{ip}:{port}/model/" + + url = build_url(ip, port, ["model"]) + logger.info(f"making a request to url - {url}") + files = {"file": open(file.filename, "rb")} r = requests.post(url, files=files) response = ServerReply(detail=r.text).dict() @@ -105,20 +125,23 @@ async def upload_model(file: UploadFile): err = traceback.format_exc() raise HTTPException(status_code=500, detail=str(err)) - -@app.get("/results/") +@app.get("/results") def download_results(): - services, _, _, _ = read_settings() - for service in services: - if "recorder" in service.lower(): - ip = services[service]["networks"]["custom-network"]["ipv4_address"] - port = int(services[service]["ports"][0].split(":")[0]) - url = f"http://{ip}:{port}/download/" + component_map, _, _ = read_settings() + + for hostname in component_map: + if "recorder" in hostname: + host = hostname + port = component_map[hostname] + + url = build_url(host, port, ["download"]) + logger.info(f"making a request to url - {url}") + response = requests.get(url) - with open(f"{service}.feather", "wb") as out_file: - shutil.copyfileobj(response.raw, out_file) - time.sleep(2) - + logger.info(f"Response from {hostname} has {len(response.content)} bytes") + with open(f"{hostname}.feather", "wb") as out_file: + out_file.write(response.content) + file_path = "results.zip" with zipfile.ZipFile(file_path, "w") as zipMe: for feather_file in find_filenames(): @@ -129,8 +152,7 @@ def download_results(): except Exception as e: raise HTTPException(status_code=404, detail="Failed download") - -@app.get("/terminate/") +@app.get("/terminate") def terminate_simulation(): try: h.helicsCloseLibrary() @@ -138,28 +160,42 @@ def terminate_simulation(): except Exception as e: raise HTTPException(status_code=404, detail="Failed download ") +def _get_feeder_info(component_map:dict): + for host in component_map: + if host == "feeder": + return host, component_map[host] def run_simulation(): - services, component_map, broker_ip, api_port = read_settings() - initstring = f"-f {len(component_map)} --name=mainbroker --loglevel=trace --local_interface={broker_ip} --localport=23404" - logging.info(f"Broker initaialization string: {initstring}") + component_map, broker_ip, api_port = read_settings() + feeder_host, feeder_port = _get_feeder_info(component_map) + logger.info(f"{broker_ip}, {api_port}") + initstring = f"-f {len(component_map)-1} --name=mainbroker --loglevel=trace --local_interface={broker_ip} --localport=23404" + logger.info(f"Broker initaialization string: {initstring}") broker = h.helicsCreateBroker("zmq", "", initstring) + app.state.broker = broker logging.info(broker) + isconnected = h.helicsBrokerIsConnected(broker) - logging.info(f"Broker connected: {isconnected}") - logging.info(str(component_map)) + logger.info(f"Broker connected: {isconnected}") + logger.info(str(component_map)) replies = [] + + broker_host = socket.gethostname() + for service_ip, service_port in component_map.items(): - url = f"http://{service_ip}:{service_port}/run/" - print(url) - myobj = { - "broker_port": 23404, - "broker_ip": broker_ip, - "api_port": api_port, - "services": services, - } - replies.append(grequests.post(url, json=myobj)) + if service_ip != broker_host: + url = build_url(service_ip, service_port, ["run"]) + logger.info(f"making a request to url - {url}") + + myobj = { + "broker_port": 23404, + "broker_ip": broker_ip, + "api_port": api_port, + "feeder_host": feeder_host, + "feeder_port": feeder_port + } + replies.append(grequests.post(url, json=myobj)) grequests.map(replies) while h.helicsBrokerIsConnected(broker): time.sleep(1) @@ -167,8 +203,7 @@ def run_simulation(): return - -@app.post("/run/") +@app.post("/run") async def run_feeder(background_tasks: BackgroundTasks): try: background_tasks.add_task(run_simulation) @@ -178,7 +213,28 @@ async def run_feeder(background_tasks: BackgroundTasks): err = traceback.format_exc() raise HTTPException(status_code=404, detail=str(err)) - +@app.post("/configure") +async def configure(wiring_diagram:WiringDiagram): + global WIRING_DIAGRAM + WIRING_DIAGRAM = wiring_diagram + + json.dump(wiring_diagram.dict(), open(WIRING_DIAGRAM_FILENAME, "w")) + for component in wiring_diagram.components: + component_model = ComponentStruct( + component = component, + links = [] + ) + for link in wiring_diagram.links: + if link.target == component.name: + component_model.links.append(link) + + url = build_url(component.host, component.container_port, ["configure"]) + logger.info(f"making a request to url - {url}") + + r = requests.post(url, json=component_model.dict()) + assert r.status_code==200, f"POST request to update configuration failed for url - {url}" + return JSONResponse(ServerReply(detail="Sucessfully updated config files for all containers").dict(), 200) + @app.get("/status/") async def status(): try: @@ -194,7 +250,7 @@ async def status(): except AttributeError as e: return {"reply": str(e), "error": True} - if __name__ == "__main__": - port = int(sys.argv[2]) - uvicorn.run(app, host="0.0.0.0", port=port) + uvicorn.run(app, host="0.0.0.0", port=int(os.environ['PORT'])) + # test_function() + # read_settings() \ No newline at end of file diff --git a/measuring_federate/Dockerfile b/measuring_federate/Dockerfile new file mode 100644 index 0000000..29f1b71 --- /dev/null +++ b/measuring_federate/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.10.6-slim-bullseye +RUN apt-get update +RUN apt-get install -y git ssh +RUN mkdir MeasurementComponent +COPY . ./MeasurementComponent +WORKDIR ./MeasurementComponent +RUN pip install -r requirements.txt +EXPOSE 5684/tcp +CMD ["python", "server.py"] diff --git a/measuring_federate/requirements.txt b/measuring_federate/requirements.txt index 9b0f0cf..1a7e631 100644 --- a/measuring_federate/requirements.txt +++ b/measuring_federate/requirements.txt @@ -8,4 +8,5 @@ fastapi uvicorn requests grequests -oedisi==1.2.1 +oedisi + diff --git a/measuring_federate/server.py b/measuring_federate/server.py index 10c4d63..f3632d3 100644 --- a/measuring_federate/server.py +++ b/measuring_federate/server.py @@ -1,20 +1,31 @@ from fastapi import FastAPI, BackgroundTasks, HTTPException -from oedisi.types.common import BrokerConfig from measuring_federate import run_simulator from fastapi.responses import JSONResponse +import traceback +import requests import uvicorn import socket -import requests +import logging import sys import json -import traceback +import os -import math -from oedisi.types.common import ServerReply, HeathCheck +from oedisi.componentframework.system_configuration import ComponentStruct +from oedisi.types.common import ServerReply, HeathCheck, DefaultFileNames +from oedisi.types.common import BrokerConfig app = FastAPI() +is_kubernetes_env = os.environ['SERVICE_NAME'] if 'SERVICE_NAME' in os.environ else None +def build_url(host:str, port:int, enpoint:list): + if is_kubernetes_env: + SERVICE_NAME = os.environ['SERVICE_NAME'] + url = f"http://{host}.{SERVICE_NAME}:{port}/" + else: + url = f"http://{host}:{port}/" + url = url + "/".join(enpoint) + return url @app.get("/") async def read_root(): @@ -26,17 +37,20 @@ async def read_root(): ).dict() return JSONResponse(response, 200) -@app.post("/run/") +@app.post("/run") async def run_model(broker_config:BrokerConfig, background_tasks: BackgroundTasks): - try: - print(broker_config) - feeder_ip = broker_config.services['oedisi_feeder']['networks']['custom-network']['ipv4_address'] - feeder_port = int(broker_config.services['oedisi_feeder']['ports'][0].split(":")[0]) - - url =f"http://{feeder_ip}:{feeder_port}/sensor/" - print(url) + logging.info(broker_config) + feeder_host = broker_config.feeder_host + feeder_port = broker_config.feeder_port + url = build_url(feeder_host, feeder_port, ['sensor']) + logging.info(url) + try: reply = requests.get(url) sensor_data = reply.json() + if not sensor_data: + msg = "empty sensor list" + raise HTTPException(404, msg) + logging.info(sensor_data) with open("sensors.json", "w") as outfile: json.dump(sensor_data, outfile) @@ -47,9 +61,23 @@ async def run_model(broker_config:BrokerConfig, background_tasks: BackgroundTask return JSONResponse(response, 200) except Exception as e: err = traceback.format_exc() - HTTPException(500,str(err)) + raise HTTPException(500,str(err)) + +@app.post("/configure") +async def configure(component_struct:ComponentStruct): + component = component_struct.component + params = component.parameters + params["name"] = component.name + links = {} + for link in component_struct.links: + links[link.target_port] = f"{link.source}/{link.source_port}" + json.dump(links , open(DefaultFileNames.INPUT_MAPPING.value, "w")) + json.dump(params , open(DefaultFileNames.STATIC_INPUTS.value, "w")) + response = ServerReply( + detail = f"Sucessfully updated configuration files." + ).dict() + return JSONResponse(response, 200) if __name__ == "__main__": - port = int(sys.argv[2]) - uvicorn.run(app, host="0.0.0.0", port=port) + uvicorn.run(app, host="0.0.0.0", port=int(os.environ['PORT'])) diff --git a/recorder/Dockerfile b/recorder/Dockerfile new file mode 100644 index 0000000..82988ba --- /dev/null +++ b/recorder/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.10.6-slim-bullseye +RUN apt-get update +RUN apt-get install -y git ssh +RUN mkdir Recorder +COPY . ./Recorder +WORKDIR ./Recorder +RUN pip install -r requirements.txt +EXPOSE 5679/tcp +CMD ["python", "server.py"] diff --git a/recorder/requirements.txt b/recorder/requirements.txt index 7cf37df..a30c5f4 100644 --- a/recorder/requirements.txt +++ b/recorder/requirements.txt @@ -6,4 +6,4 @@ numpy pandas fastapi uvicorn -oedisi==1.2.1 +oedisi diff --git a/recorder/server.py b/recorder/server.py index 9481b85..8e5249d 100644 --- a/recorder/server.py +++ b/recorder/server.py @@ -1,15 +1,22 @@ + +import traceback import logging -import os import socket +import json import sys -import traceback +import os -import uvicorn -from fastapi import BackgroundTasks, FastAPI, HTTPException +from fastapi import FastAPI, BackgroundTasks, HTTPException from fastapi.responses import FileResponse, JSONResponse -from oedisi.types.common import BrokerConfig, HeathCheck, ServerReply +import uvicorn + +from oedisi.componentframework.system_configuration import ComponentStruct +from oedisi.types.common import ServerReply, HeathCheck, DefaultFileNames +from oedisi.types.common import BrokerConfig + from record_subscription import run_simulator + app = FastAPI() @@ -28,7 +35,7 @@ def find_filenames(path_to_dir=os.getcwd(), suffix=".feather"): return [filename for filename in filenames if filename.endswith(suffix)] -@app.get("/download/") +@app.get("/download") def download_results(): file_list = find_filenames() if file_list: @@ -39,7 +46,7 @@ def download_results(): raise HTTPException(status_code=404, detail="No feather file found") -@app.post("/run/") +@app.post("/run") async def run_model(broker_config: BrokerConfig, background_tasks: BackgroundTasks): logging.info(broker_config) try: @@ -51,6 +58,20 @@ async def run_model(broker_config: BrokerConfig, background_tasks: BackgroundTas HTTPException(500, str(err)) +@app.post("/configure") +async def configure(component_struct:ComponentStruct): + component = component_struct.component + params = component.parameters + params["name"] = component.name + links = {} + for link in component_struct.links: + links[link.target_port] = f"{link.source}/{link.source_port}" + json.dump(links , open(DefaultFileNames.INPUT_MAPPING.value, "w")) + json.dump(params , open(DefaultFileNames.STATIC_INPUTS.value, "w")) + response = ServerReply( + detail = f"Sucessfully updated configuration files." + ).dict() + return JSONResponse(response, 200) + if __name__ == "__main__": - port = int(sys.argv[2]) - uvicorn.run(app, host="0.0.0.0", port=port) + uvicorn.run(app, host="0.0.0.0", port=int(os.environ['PORT'])) diff --git a/scenarios/system.json b/scenarios/system.json new file mode 100644 index 0000000..cc5f25d --- /dev/null +++ b/scenarios/system.json @@ -0,0 +1,203 @@ +{ + "name": "docker_test", + "components": [ + { + "name": "feeder", + "type": "LocalFeeder", + "host": "feeder", + "container_port": 5678, + "parameters": { + "use_smartds": false, + "user_uploads_model": false, + "profile_location": "gadal_ieee123/profiles", + "opendss_location": "gadal_ieee123/qsts", + "sensor_location": "gadal_ieee123/sensors.json", + "start_date": "2017-01-01 00:00:00", + "number_of_timesteps": 96, + "run_freq_sec": 900, + "topology_output": "topology.json" + } + }, + { + "name": "recorder_voltage_real", + "type": "Recorder", + "host": "recorder-voltage-real", + "container_port": 5679, + "parameters": {"feather_filename": "voltage_real.feather", + "csv_filename": "voltage_real.csv" + } + }, + { + "name": "recorder_voltage_imag", + "type": "Recorder", + "host": "recorder-voltage-imag", + "container_port": 5680, + "parameters": {"feather_filename": "voltage_imag.feather", + "csv_filename": "voltage_imag.csv" + } + }, + { + "name": "recorder_voltage_mag", + "type": "Recorder", + "host": "recorder-voltage-mag", + "container_port": 5681, + "parameters": {"feather_filename": "voltage_mag.feather", + "csv_filename": "voltage_mag.csv" + } + }, + { + "name": "recorder_voltage_angle", + "type": "Recorder", + "host": "recorder-voltage-angle", + "container_port": 5682, + "parameters": {"feather_filename": "voltage_angle.feather", + "csv_filename": "voltage_angle.csv" + } + }, + { + "name": "state_estimator", + "type": "StateEstimatorComponent", + "host": "state-estimator", + "container_port": 5683, + "parameters": { + "algorithm_parameters": {"tol": 1e-5} + } + }, + { + "name": "sensor_voltage_real", + "type": "MeasurementComponent", + "host": "sensor-voltage-real", + "container_port": 5684, + "parameters": { + "gaussian_variance": 0.0, + "random_percent": 0.0, + "measurement_file": "sensors.json" + } + }, + { + "name": "sensor_voltage_magnitude", + "type": "MeasurementComponent", + "host": "sensor-voltage-magnitude", + "container_port": 5685, + "parameters": { + "gaussian_variance": 0.0, + "random_percent": 0.0, + "measurement_file": "sensors.json" + } + }, + { + "name": "sensor_voltage_imaginary", + "type": "MeasurementComponent", + "host": "sensor-voltage-imaginary", + "container_port": 5686, + "parameters": { + "gaussian_variance": 0.0, + "random_percent": 0.0, + "measurement_file": "sensors.json" + } + }, + { + "name": "sensor_power_real", + "type": "MeasurementComponent", + "host": "sensor-power-real", + "container_port": 5687, + "parameters": { + "gaussian_variance": 0.0, + "random_percent": 0.0, + "measurement_file": "sensors.json" + } + }, + { + "name": "sensor_power_imaginary", + "type": "MeasurementComponent", + "host": "sensor-power-imaginary", + "container_port": 5688, + "parameters": { + "gaussian_variance": 0.0, + "random_percent": 0.0, + "measurement_file": "sensors.json" + } + } + + ], + "links": [ + { + "source": "feeder", + "source_port": "voltages_magnitude", + "target": "sensor_voltage_magnitude", + "target_port": "subscription" + }, + { + "source": "feeder", + "source_port": "voltages_real", + "target": "sensor_voltage_real", + "target_port": "subscription" + }, + { + "source": "feeder", + "source_port": "voltages_imag", + "target": "sensor_voltage_imaginary", + "target_port": "subscription" + }, + { + "source": "feeder", + "source_port": "powers_real", + "target": "sensor_power_real", + "target_port": "subscription" + }, + { + "source": "feeder", + "source_port": "powers_imag", + "target": "sensor_power_imaginary", + "target_port": "subscription" + }, + { + "source": "feeder", + "source_port": "topology", + "target": "state_estimator", + "target_port": "topology" + }, + { + "source": "sensor_voltage_magnitude", + "source_port": "publication", + "target": "state_estimator", + "target_port": "voltages_magnitude" + }, + { + "source": "sensor_power_real", + "source_port": "publication", + "target": "state_estimator", + "target_port": "powers_real" + }, + { + "source": "sensor_power_imaginary", + "source_port": "publication", + "target": "state_estimator", + "target_port": "powers_imaginary" + }, + { + "source": "feeder", + "source_port": "voltages_real", + "target": "recorder_voltage_real", + "target_port": "subscription" + }, + { + "source": "feeder", + "source_port": "voltages_imag", + "target": "recorder_voltage_imag", + "target_port": "subscription" + }, + { + "source": "state_estimator", + "source_port": "voltage_angle", + "target": "recorder_voltage_angle", + "target_port": "subscription" + }, + { + "source": "state_estimator", + "source_port": "voltage_mag", + "target": "recorder_voltage_mag", + "target_port": "subscription" + } + ] +} diff --git a/wls_federate/Dockerfile b/wls_federate/Dockerfile index ebceb7f..cc55cb6 100644 --- a/wls_federate/Dockerfile +++ b/wls_federate/Dockerfile @@ -1,16 +1,9 @@ FROM python:3.10.6-slim-bullseye - -RUN apt-get update +RUN apt-get update RUN apt-get install -y git ssh - - -RUN mkdir recorder_federate -COPY * ./recorder_federate -WORKDIR ./recorder_federate - +RUN mkdir StateEstimatorComponent +COPY . ./StateEstimatorComponent +WORKDIR ./StateEstimatorComponent RUN pip install -r requirements.txt - -EXPOSE 8766/tcp - +EXPOSE 5683/tcp CMD ["python", "server.py"] - diff --git a/wls_federate/requirements.txt b/wls_federate/requirements.txt index 255bfc0..36b217e 100644 --- a/wls_federate/requirements.txt +++ b/wls_federate/requirements.txt @@ -5,4 +5,5 @@ scipy numpy fastapi uvicorn -oedisi==1.2.1 +oedisi + diff --git a/wls_federate/server.py b/wls_federate/server.py index 948ee8f..c907802 100644 --- a/wls_federate/server.py +++ b/wls_federate/server.py @@ -1,13 +1,16 @@ -from oedisi.types.common import BrokerConfig -from state_estimator_federate import run_simulator from fastapi import FastAPI, BackgroundTasks, HTTPException +from state_estimator_federate import run_simulator +from oedisi.types.common import BrokerConfig from fastapi.responses import JSONResponse import traceback import uvicorn import socket +import json import sys +import os -from oedisi.types.common import ServerReply, HeathCheck +from oedisi.componentframework.system_configuration import ComponentStruct +from oedisi.types.common import ServerReply, HeathCheck, DefaultFileNames app = FastAPI() @@ -23,7 +26,7 @@ def read_root(): return JSONResponse(response, 200) -@app.post("/run/") +@app.post("/run") async def run_model(broker_config: BrokerConfig, background_tasks: BackgroundTasks): print(broker_config) try: @@ -37,6 +40,20 @@ async def run_model(broker_config: BrokerConfig, background_tasks: BackgroundTas HTTPException(500, str(err)) +@app.post("/configure") +async def configure(component_struct:ComponentStruct): + component = component_struct.component + params = component.parameters + params["name"] = component.name + links = {} + for link in component_struct.links: + links[link.target_port] = f"{link.source}/{link.source_port}" + json.dump(links , open(DefaultFileNames.INPUT_MAPPING.value, "w")) + json.dump(params , open(DefaultFileNames.STATIC_INPUTS.value, "w")) + response = ServerReply( + detail = f"Sucessfully updated configuration files." + ).dict() + return JSONResponse(response, 200) + if __name__ == "__main__": - port = int(sys.argv[2]) - uvicorn.run(app, host="0.0.0.0", port=port) + uvicorn.run(app, host="0.0.0.0", port=int(os.environ['PORT']))