generate_swagger.py 18.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
"""
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


13
app = Flask(__name__)
14
api = Api(app)
15

16 17 18 19 20
swagger_template = dict(
    info={
        "title": LazyString(lambda: "SoAR Backbone Message Formats"),
        "version": LazyString(lambda: "0.1"),
        "description": LazyString(
21 22
            lambda: "Backbone Message Format component for the Squad of"
            + " Autonomous Robots (SoAR) message definitions."
23 24 25 26
        ),
    },
    host=LazyString(lambda: request.host),
)
27

28

29 30
full_message_schema = api.model(
    "FullMessageSchema",
31
    {
32 33 34 35 36
        "message_ID": fields.String(
            required=True,
            description="UUID assigned to this message",
            example="b427003c-7bc8-11ed-a1eb-0242ac120002",
        ),
37
        "timestamp": fields.DateTime(
38
            required=True,
39 40
            description="Timestamp of message",
            example="2022-11-16T00:00:00Z",
41
        ),
42 43 44 45 46
        "version": fields.Float(
            required=True,
            description="Version of comms bacbone messaging format protocol",
            example=2.0,
        ),
47
        "source": fields.String(
48
            required=True,
49 50
            description="Where is this message from",
            example="autonomy_engine",
51
        ),
52
        "destination": fields.String(
53
            required=True,
54
            description="What is the destination of this message",
55 56
            example="ah-1",
        ),
57
        "encoded": fields.Boolean(
58
            required=True,
59 60 61
            description="Indicate that message raw (encoded) or decoded. "
            + "Options: encoded=True, decoded=False",
            example=False,
62
        ),
63
        "type": fields.String(
64
            required=True,
65
            description="Type of message",
66
            example="platform_status",
67
        ),
68
        "payload": fields.Raw(
69
            required=True,
70 71
            description="Content of Message",
            # example="{}",
72
        ),
73
    },
74
)
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
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",
        ),
    },
)
99
constraints_schema = api.model(
100
    "ConstraintsSchema",
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
    {
        "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,
        ),
    },
)

120 121
platform_schema = api.model(
    "PlatformSchema",
122
    {
123
        "platform_ID": fields.Integer(
124
            required=True,
125
            description="unique identifier for platform",
126
            example=1,
127
        ),
128
        "serial": fields.String(
129
            required=True,
130
            description="platform serial number",
131
            example="reav-60",
132
        ),
133
        "model": fields.String(
134
            required=True,
135
            description="platform serial number",
136
            example="reav",
137 138
        ),
        "constraints": fields.Nested(constraints_schema),
139 140
    },
)
141

142

143 144 145 146 147 148 149 150 151 152 153
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,
154 155
            description="Platform-specific modes/flight styles to perform"
            + " next action",
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
            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,
186 187
            description="DO WE NEED THIS? To trigger the platform to send"
            + " list of observations if any found",
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
            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,
214 215
            description="Sequence of actions/instructions generated by the "
            + " Autonomy Engine that should be compiled by the respective C2.",
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
        ),
    },
)

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,
237 238
            description="Points from features of interest identified by"
            + " platform if any found. DEFINE FORMAT.",
239 240 241 242
            example="",
        ),
        "region_surveyed": fields.Float(
            required=False,
243 244
            description="Region surveyed by given platform. DEFINE FORMAT."
            + " GEOJSON?",
245 246 247 248
            example="",
        ),
        "quality_of_points": fields.Float(
            required=False,
249 250
            description="Quality/strength of points from features of interest"
            + " identified by platform. DEFINE FORMAT.",
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
            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,
267 268
            description="Using GEOJSON, exact region of interest in rectangle"
            + " format polygon",
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
            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",
                        },
                    }
                ],
            },
        ),
    },
)

293 294
squad_metadata_schema = api.model(
    "SquadMetadataSchema",
295
    {
296
        "squad_ID": fields.Integer(
297
            required=True,
298
            description="Identifier of given squad",
299
            example=23,
300
        ),
301
        "no_of_platforms": fields.Integer(
302
            required=True,
303 304
            description="Number of platforms",
            example=3,
305
        ),
306 307
        "platforms": fields.List(
            fields.Nested(platform_schema),
308
            required=True,
309
            description="Squad consists of these platforms",
310
        ),
311 312
        "squad_mission_type": fields.String(
            required=True,
313 314
            description="Mission of given squad: `tracking`, `survey`,"
            + " `inspection`",
315
            example="survey",
316
        ),
317 318 319 320
        "squad_state": fields.Boolean(
            required=True,
            description="In execution, Waiting.. <define further>",
            example=False,
321
        ),
322 323 324 325 326 327 328 329 330 331
        "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.",
        ),
332 333 334
    },
)

335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358

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",
        ),
    },
)


359 360 361 362 363
gps_schema = api.model(
    "GPS",
    {
        "gps_source": fields.Float(  # TODO: TBD with partners
            required=False,
364 365
            description="Source of gps position. E.g. USBL (external), "
            + "platform itself (internal)",
366 367 368 369 370 371 372 373 374 375 376 377 378 379
            example="internal",
        ),
        "latitude_type": fields.String(
            required=False,
            description="",
            example="",
        ),
        "longitude_type": fields.String(
            required=False,
            description="",
            example="",
        ),
        "latitude": fields.Float(
            required=False,
380
            description="Latitude in <DEFINE UNITS>",
381 382 383 384
            example="",
        ),
        "longitude": fields.Float(
            required=False,
385
            description="Longitude in <DEFINE UNITS>",
386 387 388 389
            example="",
        ),
        "depth": fields.Float(
            required=False,
390
            description="Depth in <DEFINE UNITS>",
391 392
            example="",
        ),
393
        "altitude": fields.Float(
394
            required=False,
395
            description="Altitude in <DEFINE UNITS>",
396 397
            example="",
        ),
398
        # "gps_fix_seconds_ago"
399 400
    },
)
401

402 403
platform_status_message_schema = api.model(
    "platformStatusMessage",
404 405
    {
        "message": fields.Nested(
406
            full_message_schema,
407 408 409
            required=True,
            description="Message header",
        ),
410 411 412 413 414 415 416 417 418 419 420
        "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,
        ),
421 422
        "platform_state": fields.String(
            # TODO: Define dictionary with potential STATES of each platform
423
            required=False,
424
            description="Current state executed by platform. E.g. "
425 426 427
            + "STOP, IDLE, ABORT.",
            example="IDLE",
        ),
428
        "autonomy_plan_ID": fields.Integer(
429
            required=False,
430 431
            description="Last mission plan ID (according to Autonomy Engine's "
            + "mission plan number) executed by platform",
432 433
            example=1,
        ),
434
        "mission_track_ID": fields.Integer(
435 436 437 438 439 440 441
            required=False,
            description=(
                "Track number - stage in mission (e.g. "
                + "4 --> Waypoint 3 to Waypoint 4)"
            ),
            example=4,
        ),
442 443 444 445 446
        "mission_action_ID": fields.Integer(
            required=False,
            description="to add description",
            example=1,
        ),
447 448 449 450 451
        "range_to_go": fields.Float(
            required=False,
            description="Estimated distance to reach next waypoint",
            example=124.3,
        ),
452
        "speed_over_ground": fields.Float(
453
            required=False,
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
            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,
469
            description="Health status extracted by respective platform "
470 471 472
            + "if any diagnosis available checks on sensors",
            example="Warning",
        ),
473
        "gps_data": fields.List(
474 475
            fields.Nested(gps_schema),  # TODO: TBD Do we want a list of gps
            #  readings to allow > 1 reading i.e. platform + usbl
476
            required=True,
477
            description="Metadata pf each platform",
478
        ),
479
        "localisation_error": fields.Float(
480
            required=False,
481 482
            description="Localisation error at last USBL update.",
            example="",
483
        ),
484
        "usbl_fix_seconds_ago": fields.Float(
485
            required=False,
486 487
            description="",
            example="",
488
        ),
489
        "battery_remaining_capacity": fields.Float(
490
            required=True,
491 492
            description="Battery remaining capacity % provided by respective"
            + " platform/C2.",
493 494
            example=80.0,
        ),
495 496 497
        "sensor_config": fields.Nested(
            sensor_schema
        ),  # TODO: TBD Do we want a list of sensors to allow > 1 sensor
498 499 500
    },
)

501 502
acknowledgement_schema = api.model(
    "Acknowledgement",
503 504
    {
        "message": fields.Nested(
505
            full_message_schema,
506 507
            required=True,
            description="Message header",
508
        ),
509
        "message_ID": fields.Integer(
510
            required=True,
511 512 513
            description="Identifier of message received and executed with "
            + "success for mission plans sent by the Autonomy Engine.",
            example=202,
514
        ),
515
        "status": fields.String(
516
            required=True,
517 518 519
            description="Highest level of acknowledgement. I.e. `c2_received`:"
            + " Received by C2,`c2_sent`: Sent from C2"
            + " ->Platform, `executed`: Executed by platform",
520
        ),
521 522 523
    },
)

524

525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
swagger_config = {
    "headers": [],
    "specs": [
        {
            "endpoint": "swagger",
            "route": "/swagger.json",
            "rule_filter": lambda rule: True,
            "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",
    },
    "produces": ["application/json"],
    "consumes": ["application/json"],
}
548

549

550 551
swagger = Swagger(app, template=swagger_template, config=swagger_config)

552 553 554 555 556
ns1 = api.namespace(
    "message", description="Message Wrapper (Full Message Schema) Format"
)


557 558 559
@ns1.route("/wrapper")
class MessageWrapper(Resource):
    @ns1.response(200, "Success", full_message_schema)
560 561 562 563
    def get(self):
        pass


564 565 566 567 568 569
ns2 = api.namespace(
    "platform_status",
    description="platform Status Message Format",
)


570
@ns2.route("")
571 572
class platformStatus(Resource):
    @ns2.response(200, "Success", platform_status_message_schema)
573 574 575 576 577
    def get(self):
        pass


ns3 = api.namespace(
578 579 580
    "planning_configuration",
    description="Planning Configuration Format. Do we want region of "
    + "interest to be per squad, per platform or in the main schema?",
581 582
)

583

584
@ns3.route("")
585 586
class PlanningConfiguration(Resource):
    @ns3.response(200, "Success", planning_configuration_schema)
587 588 589 590
    def get(self):
        pass


591
# @api.route('/mission-plan/<str:platform_type')
592 593 594 595 596 597 598
# @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",
)

599 600 601 602 603 604

@ns4.route("")
class MissionPlan(Resource):
    @ns4.response(200, "Success", mission_plan_schema)
    def get(self):
        pass
605

606 607 608 609 610 611 612

ns5 = api.namespace(
    "observation",
    description="Observation Format --> Per platform or generic?",
)


613 614 615 616 617
@ns5.route("")
class Observation(Resource):
    @ns5.response(200, "Success", observation_schema)
    def get(self):
        pass
618 619


620
ns6 = api.namespace("acknowledgement", description="Acknowledgment Format")
621 622


623 624 625 626 627
@ns6.route("")
class Acknowledgment(Resource):
    @ns6.response(200, "Success", acknowledgement_schema)
    def get(self):
        pass
628

629

630 631
if __name__ == "__main__":
    app.run()