diff --git a/docs/generate_swagger.py b/docs/generate_swagger.py index cd442c078f41d12eed935c230ac7c70638d1fa18..f5ae4721d4935d18bbce2336aeb1be45f63ef48b 100644 --- a/docs/generate_swagger.py +++ b/docs/generate_swagger.py @@ -1,529 +1,18 @@ -""" -Generate swagger to view models/schemas. -Command: -1/ python3 generate_swagger.py -2/ Go to http://127.0.0.1:5000/soardocs -WARNING: API Endpoints are NOT functional. Purely for easy-reading. -""" -from flask import Flask, request -from flasgger import Swagger, LazyString -from flask_restx import Api, fields, Resource - +from flask import Flask +from flasgger import Swagger app = Flask(__name__) -api = Api(app) - -swagger_template = dict( - info={ - "title": LazyString(lambda: "SoAR Backbone Message Formats"), - "version": LazyString(lambda: "0.1"), - "description": LazyString( - lambda: "Backbone Message Format component for the Squad of" - + " Autonomous Robots (SoAR) message definitions." - ), - }, - host=LazyString(lambda: request.host), -) - - -full_message_schema = api.model( - "FullMessageSchema", - { - "message_ID": fields.String( - required=True, - description="UUID assigned to this message", - example="b427003c-7bc8-11ed-a1eb-0242ac120002", - ), - "timestamp": fields.DateTime( - required=True, - description="Timestamp of message", - example="2022-11-16T00:00:00Z", - ), - "version": fields.Float( - required=True, - description="Version of comms bacbone messaging format protocol", - example=2.0, - ), - "source": fields.String( - required=True, - description="Where is this message from", - example="autonomy_engine", - ), - "destination": fields.String( - required=True, - description="What is the destination of this message", - example="ah-1", - ), - "encoded": fields.Boolean( - required=True, - description="Indicate that message raw (encoded) or decoded. " - + "Options: encoded=True, decoded=False", - example=False, - ), - "type": fields.String( - required=True, - description="Type of message", - example="platform_status", - ), - "payload": fields.Raw( - required=True, - description="Content of Message", - # example="{}", - ), - }, -) -sensor_schema = api.model( - "SensorSchema", - { - "sensor_ID": fields.Integer( - required=True, - description="unique identifier for platform", - example=2, - ), - "serial": fields.String( - required=False, - description="serial number of sensor", - example="mbes-001", - ), - "sensor_status": fields.Boolean( - required=False, - description="Sensor switched on (True) or off (False)", - example=True, - ), - "additional_data": fields.Raw( - required=False, - description="Any addition fields/data to be added here", - ), - }, -) -constraints_schema = api.model( - "ConstraintsSchema", - { - "min_altitude": fields.Float( - required=True, - description="Minimum altitude set for squad.", - example=15.2, - ), - "min_velocity": fields.Float( - required=True, - description="Minimum velocity set for squad.", - example=0.1, - ), - "max_velocity": fields.Float( - required=True, - description="Maximum altitude set for squad.", - example=0.9, - ), - }, -) - -platform_schema = api.model( - "PlatformSchema", - { - "platform_ID": fields.Integer( - required=True, - description="unique identifier for platform", - example=1, - ), - "serial": fields.String( - required=True, - description="platform serial number", - example="reav-60", - ), - "model": fields.String( - required=True, - description="platform serial number", - example="reav", - ), - "constraints": fields.Nested(constraints_schema), - }, -) - - -action_schema = api.model( - "AutonomyEngineAction", - { - "action": fields.String( - required=True, - description="Autonomy Engine's action from `move`, `payload`," - + " `dive`, `send_hits`, `scanline`, `scanpoint`.", - example="move", - ), - "flight_style": fields.String( - required=False, - description="Platform-specific modes/flight styles to perform" - + " next action", - example="orbit", - ), - "latitude_waypoint": fields.Float( - required=True, - description="Next waypoint, x-coordinate", - example=-4.187143188645706, - ), - "longitude_waypoint": fields.Float( - required=True, - description="Next waypoint, y-coordinate", - example=50.37072283932642, - ), - "altitude": fields.Float( - required=False, - description="Altitude of next action", - example=15.0, - ), - "depth": fields.Float( - required=False, - description="Depth of next action", - example=15.0, - ), - "activate_payload": fields.Boolean( - required=False, - description="To activate/deactivate sensor for Autosub " - + "Hover-1 --> `MBES` sensor and for EcoSUB --> `Sidescan`", - example=True, - ), - "send_environmental_data": fields.Boolean( - required=False, - description="DO WE NEED THIS? To trigger the platform to send" - + " list of observations if any found", - example=False, - ), - }, -) - -mission_plan_schema = api.model( - "MissionPlan", - { - "message": fields.Nested( - full_message_schema, - required=True, - description="Message header", - ), - "plan_ID": fields.Integer( - required=True, - description="Identifier of given mission sequence planned", - example=1, - ), - "platform_serial": fields.String( - required=True, - description="Serial of target platform to send mission to", - example="reav-60", - ), - "plan": fields.List( - fields.Nested(action_schema), - required=True, - description="Sequence of actions/instructions generated by the " - + " Autonomy Engine that should be compiled by the respective C2.", - ), - }, -) - -observation_schema = api.model( - "Observation", - { - "message": fields.Nested( - full_message_schema, - required=True, - description="Message header", - ), - "platform_serial": fields.String( - required=True, - description="Serial of platform to sendign observations", - example="ecosub-3", - ), - # "observation_type" ==> payloads tied to different types maybe? - # properties of each observation? - "points_of_interest": fields.Float( - required=False, - description="Points from features of interest identified by" - + " platform if any found. DEFINE FORMAT.", - example="", - ), - "region_surveyed": fields.Float( - required=False, - description="Region surveyed by given platform. DEFINE FORMAT." - + " GEOJSON?", - example="", - ), - "quality_of_points": fields.Float( - required=False, - description="Quality/strength of points from features of interest" - + " identified by platform. DEFINE FORMAT.", - example=0.98, - ), - "additional_data": fields.Raw( - required=False, - description="Placeholder field for any additional data", - example={"sensor_payload": False}, - ), - }, -) - - -region_schema = api.model( - "RegionSchema", - { - "region": fields.Raw( - required=True, - description="Using GEOJSON, exact region of interest in rectangle" - + " format polygon", - example={ - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": {}, - "geometry": { - "coordinates": [ - [ - [-4.187143188645706, 50.37072283932642], - [-4.202697005964865, 50.368816892405874], - [-4.203156724702808, 50.365640144076906], - [-4.19449868846155, 50.362267670845654], - ] - ], - "type": "Polygon", - }, - } - ], - }, - ), - }, -) - -squad_metadata_schema = api.model( - "SquadMetadataSchema", - { - "squad_ID": fields.Integer( - required=True, - description="Identifier of given squad", - example=23, - ), - "no_of_platforms": fields.Integer( - required=True, - description="Number of platforms", - example=3, - ), - "platforms": fields.List( - fields.Nested(platform_schema), - required=True, - description="Squad consists of these platforms", - ), - "squad_mission_type": fields.String( - required=True, - description="Mission of given squad: `tracking`, `survey`," - + " `inspection`", - example="survey", - ), - "squad_state": fields.Boolean( - required=True, - description="In execution, Waiting.. <define further>", - example=False, - ), - "region_of_interest": fields.List( - fields.Nested(region_schema), - required=False, - description="Region of interest and exclusion zones per squad.", - ), - "exclusion_zones": fields.List( - fields.Nested(region_schema), - required=True, - description="Exclusion zones exclusion zones per squad.", - ), - }, -) - - -planning_configuration_schema = api.model( - "PlanningConfigurationSchema", - { - "message": fields.Nested( - full_message_schema, - required=True, - description="Message header", - ), - "ID": fields.Integer( - required=True, - description="Unique identifier tagged to version of this" - + " configuration plan", - example=3, - ), - "squads": fields.Nested( - squad_metadata_schema, - required=False, - description="Details of each squad", - ), - }, -) - - -gps_schema = api.model( - "GPS", - { - "gps_source": fields.Float( # TODO: TBD with partners - required=False, - description="Source of gps position. E.g. USBL (external), " - + "platform itself (internal)", - example="internal", - ), - "latitude_type": fields.String( - required=False, - description="", - example="", - ), - "longitude_type": fields.String( - required=False, - description="", - example="", - ), - "latitude": fields.Float( - required=False, - description="Latitude in <DEFINE UNITS>", - example="", - ), - "longitude": fields.Float( - required=False, - description="Longitude in <DEFINE UNITS>", - example="", - ), - "depth": fields.Float( - required=False, - description="Depth in <DEFINE UNITS>", - example="", - ), - "altitude": fields.Float( - required=False, - description="Altitude in <DEFINE UNITS>", - example="", - ), - # "gps_fix_seconds_ago" - }, -) - -platform_status_message_schema = api.model( - "platformStatusMessage", - { - "message": fields.Nested( - full_message_schema, - required=True, - description="Message header", - ), - "platform_ID": fields.Integer( - required=True, - description="unique identifier for platform", - example=1, - ), - "active": fields.Boolean( - required=False, - description="When a platform is in deployment (executing a mission" - + " plan) this should be True", - example=True, - ), - "platform_state": fields.String( - # TODO: Define dictionary with potential STATES of each platform - required=False, - description="Current state executed by platform. E.g. " - + "STOP, IDLE, ABORT.", - example="IDLE", - ), - "autonomy_plan_ID": fields.Integer( - required=False, - description="Last mission plan ID (according to Autonomy Engine's " - + "mission plan number) executed by platform", - example=1, - ), - "mission_track_ID": fields.Integer( - required=False, - description=( - "Track number - stage in mission (e.g. " - + "4 --> Waypoint 3 to Waypoint 4)" - ), - example=4, - ), - "mission_action_ID": fields.Integer( - required=False, - description="to add description", - example=1, - ), - "range_to_go": fields.Float( - required=False, - description="Estimated distance to reach next waypoint", - example=124.3, - ), - "speed_over_ground": fields.Float( - required=False, - description="", - example=124.3, - ), - "water_current_velocity": fields.Float( - required=False, - description="", - example=124.3, - ), - "thrust_applied": fields.Float( - required=False, - description="TODO: Needs further consideration", - example=124.3, - ), - "health_status": fields.String( - required=False, - description="Health status extracted by respective platform " - + "if any diagnosis available checks on sensors", - example="Warning", - ), - "gps_data": fields.List( - fields.Nested(gps_schema), # TODO: TBD Do we want a list of gps - # readings to allow > 1 reading i.e. platform + usbl - required=True, - description="Metadata pf each platform", - ), - "localisation_error": fields.Float( - required=False, - description="Localisation error at last USBL update.", - example="", - ), - "usbl_fix_seconds_ago": fields.Float( - required=False, - description="", - example="", - ), - "battery_remaining_capacity": fields.Float( - required=True, - description="Battery remaining capacity % provided by respective" - + " platform/C2.", - example=80.0, - ), - "sensor_config": fields.Nested( - sensor_schema - ), # TODO: TBD Do we want a list of sensors to allow > 1 sensor - }, -) - -acknowledgement_schema = api.model( - "Acknowledgement", - { - "message": fields.Nested( - full_message_schema, - required=True, - description="Message header", - ), - "message_ID": fields.Integer( - required=True, - description="Identifier of message received and executed with " - + "success for mission plans sent by the Autonomy Engine.", - example=202, - ), - "status": fields.String( - required=True, - description="Highest level of acknowledgement. I.e. `c2_received`:" - + " Received by C2,`c2_sent`: Sent from C2" - + " ->Platform, `executed`: Executed by platform", - ), - }, -) - swagger_config = { "headers": [], + "openapi": "3.0.2", + "swagger_ui": True, + "specs_route": "/soardocs/", + "info": { + "title": "Backbone Message Formats", + "version": "0.1", + "description": "SoAR message schemas (i.e. formats)", + }, "specs": [ { "endpoint": "swagger", @@ -532,100 +21,163 @@ swagger_config = { "model_filter": lambda tag: True, } ], - "static_url_path": "/flasgger_static", - "swagger_ui": True, - "specs_route": "/soardocs/", - "swagger": "2.0", - "basePath": "/soar", - "info": { - "title": "soar", - "version": "0.1", - "description": "SoAR message schemas", + "components": { + "schemas": { + "Message": { + "type": "object", + "required": ["message_type"], + "properties": { + "message_type": { + "type": "string", + }, + "source": { + "type": "string", + "description": "The sender.", + "example": "autonomy-engine", + }, + "destination": { + "type": "string", + "description": "Publisher topic.", + "example": "soar.noc.autosub.ah1.status", + }, + "delivery_type": { + "type": "string", + "description": "Published or broadcast", + "enum": ["broadcast", "publish"], + "example": "2.0.0", + }, + "message_ID": { + "type": "string", + "description": "An identifier for the type of " + + "message received.", + "example": "PlatformStatus", + }, + }, + "discriminator": { + "propertyName": "message_type", + }, + }, + "Coordinate": { + "allOf": [{"$ref": "#/components/schemas/Message"}], + "type": "object", + "properties": { + "latitude": { + "type": "integer", + "description": "Latitude in decimal degrees.", + "example": 54.234, + }, + "longitude": { + "type": "integer", + "description": "Longitude in decimal degrees.", + "example": -1.432, + }, + "depth": { + "type": "integer", + "description": "Target depth", + "default": 0, + "example": 50, + }, + "projection": { + "type": "integer", + "description": "EPSG Projection Code", + "example": 4326, + "default": 4326, + }, + }, + }, + "PlatformStatus": { + "allOf": [ + {"$ref": "#/components/schemas/Message"}, + ], + "type": "object", + "properties": { + "partner_ID": { + "type": "string", + "description": "An identifier for the partner " + + "owning/operating the platform.", + "example": "noc", + }, + "platform_ID": { + "type": "string", + "description": "An identifier for the platform.", + "example": "noc_ah1", + }, + "state": { + "type": "string", + "description": "Status of platform.", + "example": "idle", + }, + }, + }, + }, + }, + "paths": { + "/messages": { + "get": { + "description": "Returns all messages from the system.", + "responses": { + "200": { + "description": "A list of messages.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/" + + "schemas/Coordinate" + }, + { + "$ref": "#/components/" + + "schemas/PlatformStatus" + }, + ], + "discriminator": { + "propertyName": "message_type", + }, + } + } + }, + } + }, + } + }, + "/platformstatus": { + "get": { + "description": "Returns platform status message", + "responses": { + "200": { + "description": "Platform status message.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas" + + "/PlatformStatus" + }, + ], + "discriminator": { + "propertyName": "message_type", + }, + } + } + }, + } + }, + } + }, }, "produces": ["application/json"], "consumes": ["application/json"], } +swag = Swagger(app, config=swagger_config, merge=True) -swagger = Swagger(app, template=swagger_template, config=swagger_config) - -ns1 = api.namespace( - "message", description="Message Wrapper (Full Message Schema) Format" -) - - -@ns1.route("/wrapper") -class MessageWrapper(Resource): - @ns1.response(200, "Success", full_message_schema) - def get(self): - pass - - -ns2 = api.namespace( - "platform_status", - description="platform Status Message Format", -) - - -@ns2.route("") -class platformStatus(Resource): - @ns2.response(200, "Success", platform_status_message_schema) - def get(self): - pass - - -ns3 = api.namespace( - "planning_configuration", - description="Planning Configuration Format. Do we want region of " - + "interest to be per squad, per platform or in the main schema?", -) - - -@ns3.route("") -class PlanningConfiguration(Resource): - @ns3.response(200, "Success", planning_configuration_schema) - def get(self): - pass - - -# @api.route('/mission-plan/<str:platform_type') -# @api.doc(params={"platform_type": "The type of platform of the -# mission plan to target."}) -ns4 = api.namespace( - "mission_plan", - description="Mission Plan Format Per platform", -) - - -@ns4.route("") -class MissionPlan(Resource): - @ns4.response(200, "Success", mission_plan_schema) - def get(self): - pass - - -ns5 = api.namespace( - "observation", - description="Observation Format --> Per platform or generic?", -) - - -@ns5.route("") -class Observation(Resource): - @ns5.response(200, "Success", observation_schema) - def get(self): - pass - - -ns6 = api.namespace("acknowledgement", description="Acknowledgment Format") - - -@ns6.route("") -class Acknowledgment(Resource): - @ns6.response(200, "Success", acknowledgement_schema) - def get(self): - pass - +# app.add_url_rule( +# '/coordinates', +# view_func=Coordinates.as_view('coordinates'), +# methods=['GET'] +# ) if __name__ == "__main__": - app.run() + app.run(debug=True)