diff --git a/docs/api.yml b/docs/api.yml index 1d20809f8f6730c464114f43dbf7f5fcaddf8817..501a1dfe50658717a7bea5e4a679fa5951914013 100644 --- a/docs/api.yml +++ b/docs/api.yml @@ -32,17 +32,17 @@ paths: type: array items: $ref: '#/components/schemas/Device' - /device/{deviceName}: + /device/{deviceID}: get: summary: Gets a specific device operationId: device parameters: - in: path - name: deviceName - description: The device name + name: deviceID + description: The device id required: true schema: - type: string + type: integer responses: '200': description: The device @@ -50,17 +50,17 @@ paths: application/json: schema: $ref: '#/components/schemas/Device' - /device/{deviceName}/sensors: + /device/{deviceID}/sensors: get: summary: Gets all sensors for a specific device operationId: deviceSensors parameters: - in: path - name: deviceName - description: The device name + name: deviceID + description: The device id required: true schema: - type: string + type: integer responses: '200': description: All available sensors @@ -70,44 +70,90 @@ paths: type: array items: $ref: '#/components/schemas/Sensor' - /device/{deviceName}/sensors/{sensorName}: + + /sensors: + get: + summary: Gets all sensors + operationId: sensors + responses: + '200': + description: All available sensors + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Sensor' + /sensor/{sensorID}: get: - summary: Gets a specific sensor for a device - operationId: deviceSensor + summary: Gets a specific sensor + operationId: sensor parameters: - in: path - name: deviceName - description: The device name + name: sensorID + description: The sensor id required: true schema: - type: string + type: integer + responses: + '200': + description: The sensor + content: + application/json: + schema: + $ref: '#/components/schemas/Sensor' + /sensor/{sensorID}/measurements: + get: + summary: Gets all measurements for a specific sensor + operationId: sensorMeasurements + parameters: - in: path - name: sensorName - description: The sensor name + name: sensorID + description: The sensor id required: true schema: - type: string + type: integer responses: '200': - description: The sensor + description: All available measurements content: application/json: schema: - $ref: '#/components/schemas/Sensor' + type: array + items: + $ref: '#/components/schemas/Measurement' - /sensors: + /measurements: get: - summary: Gets all sensors - operationId: sensors + summary: Gets all measurements + operationId: measurements responses: '200': - description: All available sensors + description: All available measurements content: application/json: schema: type: array items: - $ref: '#/components/schemas/Sensor' + $ref: '#/components/schemas/Measurement' + /measurement/{measurementID}: + get: + summary: Gets a specific measurement + operationId: measurement + parameters: + - in: path + name: measurementID + description: The measurement id + required: true + schema: + type: integer + responses: + '200': + description: The measurement + content: + application/json: + schema: + $ref: '#/components/schemas/Measurement' components: schemas: @@ -149,7 +195,6 @@ components: - device_id - name - sensor_type - - value properties: id: type: integer @@ -163,9 +208,28 @@ components: sensor_type: type: string example: "temperature" + + Measurement: + type: object + required: + - id + - sensor_id + - value + - timestamp + properties: + id: + type: integer + example: 1 + sensor_id: + type: integer + example: 1 value: type: string example: "20.15" + timestamp: + type: string + format: date-time + example: "2020-09-23 20:58:00" SuccessResponse: required: diff --git a/src/blueprints/Routes.py b/src/blueprints/Routes.py index fe76b0683a8bbca1538552c3349db7dcd6611842..eb783696503ceec07347763b28c1c349ac8575d5 100644 --- a/src/blueprints/Routes.py +++ b/src/blueprints/Routes.py @@ -1,6 +1,7 @@ import json import os from enum import Enum +from typing import Dict import yaml from flask import Blueprint, request, jsonify, render_template @@ -50,33 +51,29 @@ def construct_blueprint(settings, version): database = Database(settings['database']['databasePath']) return jsonify(database.get_all_devices()) - @routes.route('/device/<deviceName>', methods=['GET']) - def get_device(deviceName): + @routes.route('/device/<int:deviceID>', methods=['GET']) + def get_device(deviceID): database = Database(settings['database']['databasePath']) - return jsonify(database.get_device(deviceName)) + return jsonify(database.get_device(deviceID)) @routes.route('/sensors', methods=['GET']) def get_all_sensors(): database = Database(settings['database']['databasePath']) return jsonify(database.get_all_sensors()) - @routes.route('/device/<deviceName>/sensors/', methods=['GET']) - def get_all_sensors_for_device(deviceName): + @routes.route('/sensor/<int:sensorID>', methods=['GET']) + def get_sensor(sensorID): database = Database(settings['database']['databasePath']) - device = database.get_device(deviceName) - if not device: - return jsonify({'success': False, 'msg': f'No device with name "{deviceName}" existing'}) - - return jsonify(database.get_all_sensors_for_device(device)) + return jsonify(database.get_sensor(sensorID)) - @routes.route('/device/<deviceName>/sensors/<sensorName>', methods=['GET']) - def get_sensor(deviceName, sensorName): + @routes.route('/device/<int:deviceID>/sensors/', methods=['GET']) + def get_all_sensors_for_device(deviceID): database = Database(settings['database']['databasePath']) - device = database.get_device(deviceName) + device = database.get_device(deviceID) if not device: - return jsonify({'success': False, 'msg': f'No device with name "{deviceName}" existing'}) + return jsonify({'success': False, 'msg': f'No device with id "{deviceID}" existing'}) - return jsonify(database.get_sensor(device['id'], sensorName)) + return jsonify(database.get_all_sensors_for_device(deviceID)) @routes.route('/device/<deviceName>', methods=['POST']) def postSensorData(deviceName): @@ -84,29 +81,49 @@ def construct_blueprint(settings, version): parameters = RequestValidator.validate(request, DeviceParameters.get_values()) database = Database(settings['database']['databasePath']) - if not database.get_device(deviceName): + if not database.get_device_by_name(deviceName): database.add_device(deviceName) - device = database.get_device(deviceName) + device = database.get_device_by_name(deviceName) sensors = parameters[DeviceParameters.SENSORS.value] for sensor in sensors: sensorParams = RequestValidator.validate_parameters(sensor, SensorParameters.get_values(), f'sensor "{sensor}"') - __add_or_update_sensor(database, device, sensorParams) + sensor = __add_sensor_if_not_exists(database, int(device['id']), sensorParams) + database.add_measurement(int(sensor['id']), sensorParams[SensorParameters.VALUE.value]) except ValidationError as e: return e.response, 400 return "" - def __add_or_update_sensor(database, device, sensorParams): + def __add_sensor_if_not_exists(database: Database, deviceID: int, sensorParams: Dict) -> Dict[str, str]: sensorName = sensorParams[SensorParameters.NAME.value] sensorType = sensorParams[SensorParameters.TYPE.value] - sensorValue = sensorParams[SensorParameters.VALUE.value] - sensor = database.get_sensor(device['id'], sensorName) + sensor = database.get_sensor_by_name_and_device_id(deviceID, sensorName) if sensor: - database.update_sensor(device, sensorName, sensorType, sensorValue) - else: - database.add_sensor(device, sensorName, sensorType, sensorValue) + return sensor + + database.add_sensor(deviceID, sensorName, sensorType) + return database.get_sensor_by_name_and_device_id(deviceID, sensorName) + + @routes.route('/measurements', methods=['GET']) + def get_all_measurements(): + database = Database(settings['database']['databasePath']) + return jsonify(database.get_all_measurements()) + + @routes.route('/measurement/<int:measurementID>', methods=['GET']) + def get_measurement(measurementID): + database = Database(settings['database']['databasePath']) + return jsonify(database.get_measurement(measurementID)) + + @routes.route('/sensor/<sensorID>/measurements', methods=['GET']) + def get_all_measurements_for_sensor(sensorID): + database = Database(settings['database']['databasePath']) + sensor = database.get_sensor(sensorID) + if not sensor: + return jsonify({'success': False, 'msg': f'No sensor with id "{sensorID}" existing'}) + + return jsonify(database.get_all_measurements_for_sensor(sensorID)) return routes diff --git a/src/logic/Database.py b/src/logic/Database.py index 59ac3f310dd96f185ab8ae7b0e2e3d5f5a3fc85d..36daa8914ded160eda901a481979ad4ea098ae79 100644 --- a/src/logic/Database.py +++ b/src/logic/Database.py @@ -1,7 +1,7 @@ import sqlite3 from datetime import datetime from enum import Enum -from typing import Tuple, Dict +from typing import Dict, List from TheCodeLabs_BaseUtils import DefaultLogger @@ -19,6 +19,7 @@ class FetchType(Enum): class Database: TABLE_DEVICE = 'device' TABLE_SENSOR = 'sensor' + TABLE_MEASUREMENT = 'measurement' DATE_FORMAT = '%Y-%m-%d %H:%M:%S' @@ -41,69 +42,93 @@ class Database: self.__query(f'''CREATE TABLE IF NOT EXISTS {self.TABLE_DEVICE} ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)''', fetch_type=FetchType.NONE) - self.__query(f'''CREATE TABLE IF NOT EXISTS sensor ( + self.__query(f'''CREATE TABLE IF NOT EXISTS {self.TABLE_SENSOR} ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id INTEGER, name TEXT NOT NULL, - type TEXT NOT NULL, + type TEXT NOT NULL)''', fetch_type=FetchType.NONE) + self.__query(f'''CREATE TABLE IF NOT EXISTS {self.TABLE_MEASUREMENT} ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sensor_id INTEGER, value TEXT NOT NULL, timestamp TEXT NOT NULL)''', fetch_type=FetchType.NONE) - def __get_cursor(self): + def __query(self, query, *args, fetch_type=FetchType.ALL): connection = sqlite3.connect(self._databasePath) connection.row_factory = Database.namedtuple_factory - return connection.cursor() - def __query(self, query, *args, fetch_type=FetchType.ALL): - cursor = self.__get_cursor() - try: - cursor.execute(query, args) + with connection: + cursor = connection.cursor() + try: + cursor.execute(query, args) - if fetch_type == FetchType.ONE: - return cursor.fetchone() - if fetch_type == FetchType.ALL: - return cursor.fetchall() - finally: - cursor.close() + if fetch_type == FetchType.ONE: + return cursor.fetchone() + if fetch_type == FetchType.ALL: + return cursor.fetchall() + finally: + cursor.close() def __get_current_datetime(self): return datetime.strftime(datetime.now(), self.DATE_FORMAT) - def get_all_devices(self): + def get_all_devices(self) -> List[Dict[str, str]]: return self.__query(f'SELECT * FROM {self.TABLE_DEVICE} ORDER BY name', fetch_type=FetchType.ALL) - def get_device(self, deviceName: str): - return self.__query(f'SELECT * FROM {self.TABLE_DEVICE} WHERE name = "{deviceName}"', fetch_type=FetchType.ONE) + def get_device(self, deviceID: int) -> Dict[str, str] or None: + return self.__query(f'SELECT * FROM {self.TABLE_DEVICE} WHERE id = ?', deviceID, fetch_type=FetchType.ONE) + + def get_device_by_name(self, deviceName: str) -> Dict[str, str] or None: + return self.__query(f'SELECT * FROM {self.TABLE_DEVICE} WHERE name = ?', deviceName, fetch_type=FetchType.ONE) def add_device(self, deviceName: str): LOGGER.debug(f'Inserting new device "{deviceName}"') self.__query(f'INSERT INTO {self.TABLE_DEVICE}(name) VALUES(?)', deviceName, fetch_type=FetchType.NONE) - def get_all_sensors(self): + def get_all_sensors(self) -> List[Dict[str, str]]: return self.__query(f'SELECT * FROM {self.TABLE_SENSOR} ORDER BY device_id, name', fetch_type=FetchType.ALL) - def get_all_sensors_for_device(self, device: Dict[str, str]): + def get_all_sensors_for_device(self, deviceID: int) -> List[Dict[str, str]]: return self.__query(f'SELECT * FROM {self.TABLE_SENSOR} WHERE device_id = ? ORDER BY name', - device['id'], + deviceID, fetch_type=FetchType.ALL) - def get_sensor(self, deviceID: int, name: str): + def get_sensor(self, sensorID: int) -> Dict[str, str] or None: + return self.__query(f'SELECT * FROM {self.TABLE_SENSOR} WHERE id = ?', + sensorID, + fetch_type=FetchType.ONE) + + def get_sensor_by_name_and_device_id(self, deviceID: int, sensorName: str) -> Dict[str, str] or None: return self.__query(f'SELECT * FROM {self.TABLE_SENSOR} WHERE device_id = ? AND name = ?', - deviceID, name, + deviceID, sensorName, fetch_type=FetchType.ONE) - def add_sensor(self, device: Dict[str, str], name: str, sensorType: str, value: str): - LOGGER.debug(f'Inserting new sensor "{name}" for device "{device["name"]}" ' - f'(type: "{sensorType}", value: "{value}")') - self.__query(f'INSERT INTO {self.TABLE_SENSOR}(name, device_id, type, value, timestamp ) ' - f'VALUES(?, ?, ?, ?, ?)', - name, device['id'], sensorType, value, self.__get_current_datetime(), + def add_sensor(self, deviceID: int, name: str, sensorType: str): + LOGGER.debug(f'Inserting new "{sensorType}" sensor "{name}" for device "{deviceID}"') + self.__query(f'INSERT INTO {self.TABLE_SENSOR}(name, device_id, type) ' + f'VALUES(?, ?, ?)', + name, deviceID, sensorType, fetch_type=FetchType.NONE) - def update_sensor(self, device: Dict[str, str], name: str, sensorType: str, value: str): - LOGGER.debug(f'Updating sensor "{name}" for device "{device["name"]}" ' - f'(type: "{sensorType}", value: "{value}")') - self.__query(f'UPDATE {self.TABLE_SENSOR} SET value = ?, timestamp = ? ' - f'WHERE device_id = ? AND name = ?', - value, self.__get_current_datetime(), device['id'], name, + def get_all_measurements(self) -> List[Dict[str, str]]: + return self.__query(f'SELECT * FROM {self.TABLE_MEASUREMENT} ORDER BY sensor_id, date(timestamp)', + fetch_type=FetchType.ALL) + + def get_measurement(self, measurementID: int) -> Dict[str, str] or None: + return self.__query(f'SELECT * FROM {self.TABLE_MEASUREMENT} WHERE id = ?', + measurementID, + fetch_type=FetchType.ALL) + + def get_all_measurements_for_sensor(self, sensorID: int) -> List[Dict[str, str]]: + return self.__query(f'SELECT * FROM {self.TABLE_MEASUREMENT} WHERE sensor_id = ? ' + f'ORDER BY sensor_id, date(timestamp)', + sensorID, + fetch_type=FetchType.ALL) + + def add_measurement(self, sensorID: int, value: str): + sensor = self.get_sensor(sensorID) + LOGGER.debug(f'Inserting new measurement for sensor "{sensor["name"]}" ' + f'(value: "{value}", device_id "{sensor["device_id"]})') + self.__query(f'INSERT INTO {self.TABLE_MEASUREMENT}(sensor_id, value, timestamp ) VALUES(?, ?, ?)', + sensorID, value, self.__get_current_datetime(), fetch_type=FetchType.NONE)