Commit 3a41f4a2 authored by Trishna Saeharaseelan's avatar Trishna Saeharaseelan
Browse files

refactor(docs): change swagger2.0 to openapi3.0 to support oneOf

parent 745ee8c4
""" from flask import Flask
Generate swagger to view models/schemas. from flasgger import Swagger
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
app = Flask(__name__) 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 = { swagger_config = {
"headers": [], "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": [ "specs": [
{ {
"endpoint": "swagger", "endpoint": "swagger",
...@@ -532,100 +21,163 @@ swagger_config = { ...@@ -532,100 +21,163 @@ swagger_config = {
"model_filter": lambda tag: True, "model_filter": lambda tag: True,
} }
], ],
"static_url_path": "/flasgger_static", "components": {
"swagger_ui": True, "schemas": {
"specs_route": "/soardocs/", "Message": {
"swagger": "2.0", "type": "object",
"basePath": "/soar", "required": ["message_type"],
"info": { "properties": {
"title": "soar", "message_type": {
"version": "0.1", "type": "string",
"description": "SoAR message schemas", },
"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"], "produces": ["application/json"],
"consumes": ["application/json"], "consumes": ["application/json"],
} }
swag = Swagger(app, config=swagger_config, merge=True)
swagger = Swagger(app, template=swagger_template, config=swagger_config) # app.add_url_rule(
# '/coordinates',
ns1 = api.namespace( # view_func=Coordinates.as_view('coordinates'),
"message", description="Message Wrapper (Full Message Schema) Format" # methods=['GET']
) # )
@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
if __name__ == "__main__": if __name__ == "__main__":
app.run() app.run(debug=True)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment