From 10f0dfff2822368f954c5ab36457619f9cb04844 Mon Sep 17 00:00:00 2001 From: Andreas Menzel Date: Wed, 22 Mar 2023 15:28:13 +0100 Subject: [PATCH] Implement data booking with CChainLink --- Documentation/API/ASK.md | 74 +++++++++------ Documentation/API/TELL.md | 177 +++++++++++++++++++++++------------ code/flaskr/ask.py | 48 +++++----- code/flaskr/schema.sql | 6 ++ code/flaskr/tell.py | 38 ++++++-- code/functions_collection.py | 80 +++++++++++++++- 6 files changed, 301 insertions(+), 122 deletions(-) diff --git a/Documentation/API/ASK.md b/Documentation/API/ASK.md index edf9412..b8638b2 100644 --- a/Documentation/API/ASK.md +++ b/Documentation/API/ASK.md @@ -452,20 +452,21 @@ The `data_type` is `aircraft_location`. **response_data field** -| FIELD | TYPE | VALUE SET? | INFORMATION | -|--------------------------|---------|------------|------------------------------------------------| -| gps_signal_level | int | always | 0 (no gps signal) - 5 (very good gps signal) | -| gps_satellites_connected | int | always | Number of gps-satellites connected. | -| gps_valid | boolean | always | Whether the drone has a (valid) gps-signal. | -| gps_lat | float | always | Latitude. | -| gps_lon | float | always | Longitude. | -| altitude | float | always | In meters. | -| velocity_x | float | always | Velocity X (towards north) in meters / second. | -| velocity_y | float | always | Velocity Y (towards east) in meters / second. | -| velocity_z | float | always | Velocity Z (towards down) in meters / second. | -| pitch | float | always | [-180;180]. | -| yaw | float | always | [-180;180]. | -| roll | float | always | [-180;180]. | +| FIELD | TYPE | VALUE SET? | INFORMATION | +|--------------------------|---------|------------|----------------------------------------------------| +| transaction_uuid | string | always | UUID of the dataset transaction in the blockchain. | +| gps_signal_level | int | always | 0 (no gps signal) - 5 (very good gps signal) | +| gps_satellites_connected | int | always | Number of gps-satellites connected. | +| gps_valid | boolean | always | Whether the drone has a (valid) gps-signal. | +| gps_lat | float | always | Latitude. | +| gps_lon | float | always | Longitude. | +| altitude | float | always | In meters. | +| velocity_x | float | always | Velocity X (towards north) in meters / second. | +| velocity_y | float | always | Velocity Y (towards east) in meters / second. | +| velocity_z | float | always | Velocity Z (towards down) in meters / second. | +| pitch | float | always | [-180;180]. | +| yaw | float | always | [-180;180]. | +| roll | float | always | [-180;180]. |
Sample response

@@ -475,6 +476,8 @@ The `data_type` is `aircraft_location`. "errors": [], "warnings": [], "response_data": { + "transaction_uuid": "00000000-0000-0000-000000000000", + "gps_signal_level": 5, "gps_satellites_connected": 12, @@ -542,12 +545,13 @@ The `data_type` is `aircraft_power`. **response_data field** -| FIELD | TYPE | VALUE SET? | INFORMATION | -|---------------------------|-------|------------|-------------| -| battery_remaining | int | always | In mAh. | -| battery_remaining_percent | int | always | In %. | -| remaining_flight_time | int | always | In seconds. | -| remaining_flight_radius | float | always | In meters. | +| FIELD | TYPE | VALUE SET? | INFORMATION | +|---------------------------|--------|------------|----------------------------------------------------| +| transaction_uuid | string | always | UUID of the dataset transaction in the blockchain. | +| battery_remaining | int | always | In mAh. | +| battery_remaining_percent | int | always | In %. | +| remaining_flight_time | int | always | In seconds. | +| remaining_flight_radius | float | always | In meters. |

Sample response

@@ -557,8 +561,11 @@ The `data_type` is `aircraft_power`. "errors": [], "warnings": [], "response_data": { + "transaction_uuid": "00000000-0000-0000-000000000000", + "battery_remaining": 4500, "battery_remaining_percent": 42, + "remaining_flight_time": 550, "remaining_flight_radius": 4320.5 } @@ -613,17 +620,18 @@ The `data_type` is `flight_data`. **response_data field** -| FIELD | TYPE | VALUE SET? | INFORMATION | -|-------------------|-------------------------|------------|-----------------------------| -| takeoff_time | int | always | UNIX timestamp. | -| takeoff_gps_valid | boolean | always | GPS-coordinates valid? | -| takeoff_gps_lat | float | always | Latitude. | -| takeoff_gps_lon | float | always | Longitude. | -| landing_time | int | always | UNIX timestamp. | -| landing_gps_valid | boolean | always | GPS-coordinates valid? | -| landing_gps_lat | float | always | Latitude. | -| landing_gps_lon | float | always | Longitude. | -| operation_modes | [string] as json-string | always | The last X Operation Modes. | +| FIELD | TYPE | VALUE SET? | INFORMATION | +|-------------------|-------------------------|------------|----------------------------------------------------| +| transaction_uuid | string | always | UUID of the dataset transaction in the blockchain. | +| takeoff_time | int | always | UNIX timestamp. | +| takeoff_gps_valid | boolean | always | GPS-coordinates valid? | +| takeoff_gps_lat | float | always | Latitude. | +| takeoff_gps_lon | float | always | Longitude. | +| landing_time | int | always | UNIX timestamp. | +| landing_gps_valid | boolean | always | GPS-coordinates valid? | +| landing_gps_lat | float | always | Latitude. | +| landing_gps_lon | float | always | Longitude. | +| operation_modes | [string] as json-string | always | The last X Operation Modes. |

Sample response

@@ -633,14 +641,18 @@ The `data_type` is `flight_data`. "errors": [], "warnings": [], "response_data": { + "transaction_uuid": "00000000-0000-0000-000000000000", + "takeoff_time": 1678264333, "takeoff_gps_valid": "1", "takeoff_gps_lat": 48.26586, "takeoff_gps_lon": 11.67436, + "landing_time": 1678264389, "landing_gps_valid": "1", "landing_gps_lat": 48.26586, "landing_gps_lon": 11.67436, + "operation_modes": "[\"OnGround\", \"Landing\", \"Hovering\", \"TakeOff\", \"OnGround\"]" } } diff --git a/Documentation/API/TELL.md b/Documentation/API/TELL.md index 43dd480..d4bd896 100644 --- a/Documentation/API/TELL.md +++ b/Documentation/API/TELL.md @@ -134,64 +134,6 @@ If one or more warnings occured, they are added to the warnings list. ## Interfaces -### aircraft_location - -One can send information about the location of a drone. - -#### Request - -The `data_type` is `aircraft_location`. - -**Payload - data field (required)** - -| FIELD | TYPE | REQ / OPT | INFORMATION | -|--------------------------|---------|-----------|----------------------------------------------| -| gps_signal_level | int | required | 0 (no gps signal) - 5 (very good gps signal) | -| gps_satellites_connected | int | required | Number of gps-satellites connected. | -| gps_valid | boolean | required | Whether the drone has a (valid) gps-signal. | -| gps_lat | float | required | Latitude. | -| gps_lon | float | required | Longitude. | -| altitude | float | required | Altitude in meters. | -| velocity_x | float | required | Velocity X in meters / second. | -| velocity_y | float | required | Velocity Y in meters / second. | -| velocity_z | float | required | Velocity Z in meters / second. | -| pitch | float | required | [-180;180]. | -| yaw | float | required | [-180;180]. | -| roll | float | required | [-180;180]. | - -

Sample payload

- -```json -{ - "drone_id": "demo_drone", - "data_type": "aircraft_location", - "data": { - "gps_signal_level": 5, - "gps_satellites_connected": 12, - - "gps_valid": true, - "gps_lat": 48.26586, - "gps_lon": 11.67436, - - "altitude": 42, - - "velocity_x": 0, - "velocity_y": 0, - "velocity_z": 0, - - "pitch": 0, - "yaw": 0, - "roll": 0 - } -} -``` - -

- -#### Response - -Standard response. The `response_data` field is never set. - ### intersection_location One can send information about the location of an intersection. The intersection @@ -268,6 +210,83 @@ corridor. Standard response. The `response_data` field is never set. +### aircraft_location + +One can send information about the location of a drone. + +#### Request + +The `data_type` is `aircraft_location`. + +**Payload - data field (required)** + +| FIELD | TYPE | REQ / OPT | INFORMATION | +|--------------------------|---------|-----------|----------------------------------------------| +| gps_signal_level | int | required | 0 (no gps signal) - 5 (very good gps signal) | +| gps_satellites_connected | int | required | Number of gps-satellites connected. | +| gps_valid | boolean | required | Whether the drone has a (valid) gps-signal. | +| gps_lat | float | required | Latitude. | +| gps_lon | float | required | Longitude. | +| altitude | float | required | Altitude in meters. | +| velocity_x | float | required | Velocity X in meters / second. | +| velocity_y | float | required | Velocity Y in meters / second. | +| velocity_z | float | required | Velocity Z in meters / second. | +| pitch | float | required | [-180;180]. | +| yaw | float | required | [-180;180]. | +| roll | float | required | [-180;180]. | + +
Sample payload

+ +```json +{ + "drone_id": "demo_drone", + "data_type": "aircraft_location", + "data": { + "gps_signal_level": 5, + "gps_satellites_connected": 12, + + "gps_valid": true, + "gps_lat": 48.26586, + "gps_lon": 11.67436, + + "altitude": 42, + + "velocity_x": 0, + "velocity_y": 0, + "velocity_z": 0, + + "pitch": 0, + "yaw": 0, + "roll": 0 + } +} +``` + +

+ +#### Response + +**response_data field** + +| FIELD | TYPE | VALUE SET? | INFORMATION | +|------------------|--------|------------|----------------------------------------------------| +| transaction_uuid | string | always | UUID of the dataset transaction in the blockchain. | + +
Sample response

+ +```json +{ + "executed": true, + "errors": [], + "warnings": [], + "response_data": { + "transaction_uuid": "00000000-0000-0000-000000000000", + } +} +``` + +

+ ### aircraft_power One can send information about the state of charge and range of a drone. @@ -304,7 +323,26 @@ The `data_type` is `aircraft_power`. #### Response -Standard response. The `response_data` field is never set. +**response_data field** + +| FIELD | TYPE | VALUE SET? | INFORMATION | +|------------------|--------|------------|----------------------------------------------------| +| transaction_uuid | string | always | UUID of the dataset transaction in the blockchain. | + +
Sample response

+ +```json +{ + "executed": true, + "errors": [], + "warnings": [], + "response_data": { + "transaction_uuid": "00000000-0000-0000-000000000000", + } +} +``` + +

### flight_data @@ -353,4 +391,23 @@ The `data_type` is `flight_data`. #### Response -Standard response. The `response_data` field is never set. +**response_data field** + +| FIELD | TYPE | VALUE SET? | INFORMATION | +|------------------|--------|------------|----------------------------------------------------| +| transaction_uuid | string | always | UUID of the dataset transaction in the blockchain. | + +
Sample response

+ +```json +{ + "executed": true, + "errors": [], + "warnings": [], + "response_data": { + "transaction_uuid": "00000000-0000-0000-000000000000", + } +} +``` + +

diff --git a/code/flaskr/ask.py b/code/flaskr/ask.py index b3091a0..51e08a6 100644 --- a/code/flaskr/ask.py +++ b/code/flaskr/ask.py @@ -366,6 +366,8 @@ def ask_aircraft_location(): if not db_drone_info is None: response['response_data'] = { + 'transaction_uuid': db_drone_info['transaction_uuid'], + 'gps_signal_level': db_drone_info['gps_signal_level'], 'gps_satellites_connected': db_drone_info['gps_satellites_connected'], @@ -446,10 +448,10 @@ def ask_aircraft_power(): return jsonify(response) # Get aircraft_power data - db_drone_info = None + db_aircraft_power_info = None if data is None: # Get latest entry - db_drone_info = db.execute(""" + db_aircraft_power_info = db.execute(""" SELECT * FROM aircraft_power WHERE drone_id = ? ORDER BY id DESC @@ -471,19 +473,21 @@ def ask_aircraft_power(): if not response['executed']: return jsonify(response) - db_drone_info = db.execute(""" + db_aircraft_power_info = db.execute(""" SELECT * FROM aircraft_power WHERE drone_id = ? AND id = ? """, (drone_id, data_id,)).fetchone() - if not db_drone_info is None: + if not db_aircraft_power_info is None: response['response_data'] = { - 'battery_remaining': db_drone_info['battery_remaining'], - 'battery_remaining_percent': db_drone_info['battery_remaining_percent'], + 'transaction_uuid': db_aircraft_power_info['transaction_uuid'], + + 'battery_remaining': db_aircraft_power_info['battery_remaining'], + 'battery_remaining_percent': db_aircraft_power_info['battery_remaining_percent'], - 'remaining_flight_time': db_drone_info['remaining_flight_time'], - 'remaining_flight_radius': db_drone_info['remaining_flight_radius'] + 'remaining_flight_time': db_aircraft_power_info['remaining_flight_time'], + 'remaining_flight_radius': db_aircraft_power_info['remaining_flight_radius'] } response = jsonify(response) @@ -548,10 +552,10 @@ def ask_flight_data(): return jsonify(response) # Get aircraft_power data - db_drone_info = None + db_flight_data_info = None if data is None: # Get latest entry - db_drone_info = db.execute(""" + db_flight_data_info = db.execute(""" SELECT * FROM flight_data WHERE drone_id = ? ORDER BY id DESC @@ -573,25 +577,27 @@ def ask_flight_data(): if not response['executed']: return jsonify(response) - db_drone_info = db.execute(""" + db_flight_data_info = db.execute(""" SELECT * FROM flight_data WHERE drone_id = ? AND id = ? """, (drone_id, data_id,)).fetchone() - if not db_drone_info is None: + if not db_flight_data_info is None: response['response_data'] = { - 'takeoff_time': db_drone_info['takeoff_time'], - 'takeoff_gps_valid': db_drone_info['takeoff_gps_valid'], - 'takeoff_gps_lat': db_drone_info['takeoff_gps_lat'], - 'takeoff_gps_lon': db_drone_info['takeoff_gps_lon'], + 'transaction_uuid': db_flight_data_info['transaction_uuid'], + + 'takeoff_time': db_flight_data_info['takeoff_time'], + 'takeoff_gps_valid': db_flight_data_info['takeoff_gps_valid'], + 'takeoff_gps_lat': db_flight_data_info['takeoff_gps_lat'], + 'takeoff_gps_lon': db_flight_data_info['takeoff_gps_lon'], - 'landing_time': db_drone_info['landing_time'], - 'landing_gps_valid': db_drone_info['landing_gps_valid'], - 'landing_gps_lat': db_drone_info['landing_gps_lat'], - 'landing_gps_lon': db_drone_info['landing_gps_lon'], + 'landing_time': db_flight_data_info['landing_time'], + 'landing_gps_valid': db_flight_data_info['landing_gps_valid'], + 'landing_gps_lat': db_flight_data_info['landing_gps_lat'], + 'landing_gps_lon': db_flight_data_info['landing_gps_lon'], - 'operation_modes': db_drone_info['operation_modes'] + 'operation_modes': db_flight_data_info['operation_modes'] } response = jsonify(response) diff --git a/code/flaskr/schema.sql b/code/flaskr/schema.sql index d4e9b66..abfa050 100644 --- a/code/flaskr/schema.sql +++ b/code/flaskr/schema.sql @@ -20,6 +20,8 @@ CREATE TABLE drones ( CREATE TABLE aircraft_location ( id INTEGER PRIMARY KEY AUTOINCREMENT, + transaction_uuid TEXT, + drone_id TEXT NOT NULL, gps_signal_level INTEGER, @@ -39,6 +41,8 @@ CREATE TABLE aircraft_location ( CREATE TABLE aircraft_power ( id INTEGER PRIMARY KEY AUTOINCREMENT, + transaction_uuid TEXT, + drone_id TEXT NOT NULL, battery_remaining INTEGER, @@ -50,6 +54,8 @@ CREATE TABLE aircraft_power ( CREATE TABLE flight_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, + transaction_uuid TEXT, + drone_id TEXT NOT NULL, takeoff_time INTEGER, diff --git a/code/flaskr/tell.py b/code/flaskr/tell.py index 5a13ed6..fbc7906 100644 --- a/code/flaskr/tell.py +++ b/code/flaskr/tell.py @@ -176,7 +176,7 @@ def tell_corridor_location(): f'Intersection A with id "{intersection_a}" not found.', False ) - + # Check if intersection_b exists tmp_db_intersection_b_id = db.execute(""" SELECT id @@ -222,7 +222,7 @@ def tell_corridor_location(): @bp.route('aircraft_location') def tell_aircraft_location(): - response = get_response_template() + response = get_response_template(response_data=True) # Get data formatted as JSON string payload_as_json_string = request.values.get('payload') @@ -337,9 +337,16 @@ def tell_aircraft_location(): if not response['executed']: return jsonify(response) + # Save data in blockchain and get transaction_uuid + response, transaction_uuid = save_data_in_blockchain( + response, payload_as_json_string) + response['response_data'] = {} + response['response_data']['transaction_uuid'] = transaction_uuid + try: db.execute(""" INSERT INTO aircraft_location( + transaction_uuid, drone_id, gps_signal_level, gps_satellites_connected, @@ -355,9 +362,9 @@ def tell_aircraft_location(): roll ) VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) - """, (drone_id, gps_signal_level, gps_satellites_connected, gps_valid, gps_lat, gps_lon, altitude, velocity_x, velocity_y, velocity_z, pitch, yaw, roll,) + """, (transaction_uuid, drone_id, gps_signal_level, gps_satellites_connected, gps_valid, gps_lat, gps_lon, altitude, velocity_x, velocity_y, velocity_z, pitch, yaw, roll,) ) db.commit() @@ -374,7 +381,7 @@ def tell_aircraft_location(): @bp.route('aircraft_power') def tell_aircraft_power(): - response = get_response_template() + response = get_response_template(response_data=True) # Get data formatted as JSON string payload_as_json_string = request.values.get('payload') @@ -461,9 +468,16 @@ def tell_aircraft_power(): if not response['executed']: return jsonify(response) + # Save data in blockchain and get transaction_uuid + response, transaction_uuid = save_data_in_blockchain( + response, payload_as_json_string) + response['response_data'] = {} + response['response_data']['transaction_uuid'] = transaction_uuid + try: db.execute(""" INSERT INTO aircraft_power( + transaction_uuid, drone_id, battery_remaining, battery_remaining_percent, @@ -473,7 +487,7 @@ def tell_aircraft_power(): VALUES ( ?, ?, ?, ?, ? ) - """, (drone_id, battery_remaining, battery_remaining_percent, remaining_flight_time, remaining_flight_radius,) + """, (transaction_uuid, drone_id, battery_remaining, battery_remaining_percent, remaining_flight_time, remaining_flight_radius,) ) db.commit() @@ -490,7 +504,7 @@ def tell_aircraft_power(): @bp.route('flight_data') def tell_flight_data(): - response = get_response_template() + response = get_response_template(response_data=True) # Get data formatted as JSON string payload_as_json_string = request.values.get('payload') @@ -607,11 +621,17 @@ def tell_flight_data(): if not response['executed']: return jsonify(response) - str_operation_modes = json.dumps(operation_modes) + # Save data in blockchain and get transaction_uuid + response, transaction_uuid = save_data_in_blockchain( + response, payload_as_json_string) + response['response_data'] = {} + response['response_data']['transaction_uuid'] = transaction_uuid + str_operation_modes = json.dumps(operation_modes) try: db.execute(""" INSERT INTO flight_data( + transaction_uuid, drone_id, takeoff_time, takeoff_gps_valid, @@ -626,7 +646,7 @@ def tell_flight_data(): VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) - """, (drone_id, takeoff_time, takeoff_gps_valid, takeoff_gps_lat, takeoff_gps_lon, landing_time, landing_gps_valid, landing_gps_lat, landing_gps_lon, str_operation_modes,) + """, (transaction_uuid, drone_id, takeoff_time, takeoff_gps_valid, takeoff_gps_lat, takeoff_gps_lon, landing_time, landing_gps_valid, landing_gps_lat, landing_gps_lon, str_operation_modes,) ) db.commit() diff --git a/code/functions_collection.py b/code/functions_collection.py index 45885f4..38fdb88 100644 --- a/code/functions_collection.py +++ b/code/functions_collection.py @@ -1,5 +1,10 @@ +import requests + # This file holds variables and functions that can / will be used by all modules +cchainlink_url = 'http://adds-demo.an-men.de:8080/' + + # Function copied from "https://github.com/python/cpython/blob/b2076b00710c4366dcfe6cd236e480d68a3c38b7/Lib/distutils/util.py#L308" def strtobool (val): """Convert a string representation of truth to true (1) or false (0). @@ -84,4 +89,77 @@ def check_argument_type(response, argument, argument_name, data_type, err_id = - f'Could not convert "{argument_name}" to {data_type}.' ) - return response, argument \ No newline at end of file + return response, argument + + +def save_data_in_blockchain(response, payload): + transaction_uuid = None + + cchainlink_response = None + try: + cchainlink_response = requests.get(cchainlink_url + 'book_data?payload=' + payload) + except: + response = add_error_to_response( + response, + -1, + 'Could not reach C-Chain Link. Data was not booked in the blockchain!', + False + ) + + cchainlink_response_json = None + if not cchainlink_response is None: + try: + cchainlink_response_json = cchainlink_response.json() + except: + response = add_error_to_response( + response, + -1, + 'Invalid response from C-Chain Link. Data may not have been booked in the blockchain!', + False + ) + + if not cchainlink_response_json is None: + try: + transaction_uuid = cchainlink_response_json['response_data']['transaction_uuid'] + except: + response = add_error_to_response( + response, + -1, + 'Invalid response from C-Chain Link (transaction_uuid not transmitted properly). Data may not have been booked in the blockchain!', + False + ) + + try: + for err in cchainlink_response_json.get('errors'): + response = add_error_to_response( + response, + err.get('err_id'), + 'From C-Chain Link: ' + err.get('err_msg') + ) + except: + response = add_error_to_response( + response, + -1, + 'Invalid response from C-Chain Link (errors not transmitted properly). Data may not have been booked in the blockchain!', + False + ) + + try: + for warn in cchainlink_response_json.get('warnings'): + response = add_warning_to_response( + response, + warn.get('warn_id'), + 'From C-Chain Link: ' + warn.get('warn_msg') + ) + except: + response = add_error_to_response( + response, + -1, + 'Invalid response from C-Chain Link (warnings not transmitted properly). Data may not have been booked in the blockchain!', + False + ) + + if not cchainlink_response_json is None and not cchainlink_response_json.get('executed'): + response['executed'] = False + + return response, transaction_uuid