diff --git a/app/controllers/constants.py b/app/controllers/constants.py index dc2a0a187695b4cd85c101c7c9a3681a4a891fbb..47bfc47214dc2448ccfbb529f8e51481ce9f94fc 100644 --- a/app/controllers/constants.py +++ b/app/controllers/constants.py @@ -2,4 +2,5 @@ GATEWAY_TYPES = { 'senseware': 1, 'awair': 2, 'other': 3, + 'machineq': 4, } diff --git a/app/controllers/data.py b/app/controllers/data.py index a2c1a2091b8b6a9d9b75d3526b63487918a146e2..c0629261e09a3249115d70764c16fe328a40e80a 100644 --- a/app/controllers/data.py +++ b/app/controllers/data.py @@ -18,15 +18,20 @@ class DataController(RestController): def index(self, filter_data): ''' - Takes in device data like + filter_data values device[]=$DEVICE_TYPE::$DEVICE_SPECIFIC_ID_1::$DEVICE_SPECIFIC_ID_2_OPTIONAL + unit_id + from + to + date format = Wed, 15 Aug 2018 01:44:58 GMT ''' if 'device[]' not in filter_data: raise BadRequest("'device[]' is a required field in the query params") unit_id = filter_data.get('unit_id') date = filter_data.get('from') - devices = filter_data.getlist('device[]') - metadata_id_dict = {} + to_date = filter_data.get('to') + devices = filter_data.getlist('device[]') # Devices for which we need to find metadata IDs + metadata_id_dict = {} # Metadata IDs associated to sensors for device in devices: device_info = device.split('::') device_type = device_info[0] @@ -98,8 +103,42 @@ class DataController(RestController): 'metadata_id': metadata_id, 'data': [], } + elif device_type == 'machineq': + if len(device_info) < 2: + raise BadRequest("deveui is a required field in the device[] query param if the device is a machineq") + deveui = device_info[1] + query = ''' + SELECT meta.id as metadata_id, unit.description as unit FROM + iot.metadata as meta + INNER JOIN iot.unit ON unit.id = meta.unit_id + INNER JOIN iot.metadata_machineq as machineq on machineq.metadata_id = meta.id + WHERE machineq.deveui=:param + ''' + params = {'param': deveui} + if unit_id: + query += ''' + AND unit.id=:unit_param + ''' + params['unit_param'] = unit_id - # No metadata found for the specified sensor + res = db.session.execute(query, params) + + metadata_id_rows = res.fetchall() + + for metadata_id_row in metadata_id_rows: + metadata_id = metadata_id_row[0] + metadata_id_dict[metadata_id] = { + 'type': device_type, + 'deveui': deveui, + 'unit': metadata_id_row[1], + 'metadata_id': metadata_id, + 'data': [], + } + + ''' + No metadata id found for the specified sensor + can't fetch data from redshift + ''' if len(metadata_id_dict.keys()) == 0: return [] from_clause = '' @@ -107,6 +146,10 @@ class DataController(RestController): from_clause = ''' AND ts > %s ''' + if to_date: + from_clause += ''' + AND ts < %s + ''' sql = ''' SELECT @@ -127,6 +170,8 @@ class DataController(RestController): insert_tuple += (metadata_id,) if date: insert_tuple += (date,) + if to_date: + insert_tuple += (to_date,) redshift_db = get_redshift_db(current_app) cur = redshift_db.cursor(cursor_factory=psycopg2.extras.DictCursor) diff --git a/app/controllers/gateway.py b/app/controllers/gateway.py index 04affbcc5b7e021406f76ddb87a985b7293ac98a..99b7db983376ffedbe3bc4348ce84d28e5d75422 100644 --- a/app/controllers/gateway.py +++ b/app/controllers/gateway.py @@ -6,6 +6,7 @@ from ..forms.gateway import GatewayForm from ..models.gateway import Gateway from ..controllers.data import DataController from ..controllers.senseware_node import SensewareNodeController +from ..controllers.machineq_node import MachineqNodeController from .constants import ( GATEWAY_TYPES, ) @@ -25,14 +26,17 @@ class GatewayController(RestController): def index(self, filter_data): """ Retrieve a list of gateways + return data if data is present in query params """ gateway_result = super().index(filter_data) - """ New way of accessing data """ + # New way of accessing data if 'data' in filter_data and gateway_result: # Access data for gateways that don't have seperate nodes args = MultiDict([]) if 'from' in filter_data: args.add('from', filter_data.get('from')) + if 'to' in filter_data: + args.add('to', filter_data.get('to')) if 'unit_id' in filter_data: args.add('unit_id', filter_data.get('unit_id')) for gateway in gateway_result: @@ -49,10 +53,21 @@ class GatewayController(RestController): )) node['data'] = [] gateway['nodes'] = node_result - data_result_list = DataController().index(args) - # Parse through the data response and add it to the correct gateways and nodes + if gateway['sensor_type'] == GATEWAY_TYPES['machineq']: + node_args = MultiDict([('gateway_id', gateway['id'])]) + node_result = MachineqNodeController().index(node_args) + for node in node_result: + args.add('device[]', 'machineq::{}'.format( + node['deveui'], + )) + node['data'] = [] + gateway['nodes'] = node_result + data_result_list = DataController().index(args) + ''' + Parse through the data response and add it to the correct gateways and nodes + Loop over results in data from data controller + ''' for data_result in data_result_list: - for gateway in gateway_result: if GATEWAY_TYPES[data_result['type']] == gateway['sensor_type']: if data_result['type'] == 'awair': @@ -65,6 +80,10 @@ class GatewayController(RestController): data_result['sn'] == gateway['gateway_serial'] ): node['data'].extend(data_result['data']) + elif data_result['type'] == 'machineq': + for node in gateway['nodes']: + if data_result['deveui'] == node['deveui']: + node['data'].extend(data_result['data']) elif 'nodes' in filter_data and gateway_result: for gateway in gateway_result: @@ -72,4 +91,8 @@ class GatewayController(RestController): node_args = MultiDict([('gateway_id', gateway['id'])]) node_result = SensewareNodeController().index(node_args) gateway['nodes'] = node_result + elif gateway['sensor_type'] == GATEWAY_TYPES['machineq']: + node_args = MultiDict([('gateway_id', gateway['id'])]) + node_result = MachineqNodeController().index(node_args) + gateway['nodes'] = node_result return gateway_result diff --git a/app/controllers/machineq_node.py b/app/controllers/machineq_node.py new file mode 100644 index 0000000000000000000000000000000000000000..650dfb17bfa9ff437481a9da9a0a07110b4e0d25 --- /dev/null +++ b/app/controllers/machineq_node.py @@ -0,0 +1,14 @@ +from .base import RestController +from ..forms.machineq_node import MachineqNodeForm +from ..models.machineq_node import MachineqNode + +class MachineqNodeController(RestController): + """A machineq node controller.""" + Model = MachineqNode + filters = { + 'gateway_id': lambda d: MachineqNode.gateway_id == d['gateway_id'], + } + + def get_form(self, filter_data): + """Return the machineq node form.""" + return MachineqNodeForm diff --git a/app/forms/machineq_node.py b/app/forms/machineq_node.py new file mode 100644 index 0000000000000000000000000000000000000000..ce8df95bd50f81cbebfba4e01fd8e4e0dec22ffa --- /dev/null +++ b/app/forms/machineq_node.py @@ -0,0 +1,44 @@ +import wtforms as wtf +from .base import Form, UserForm + +class NullableIntegerField(wtf.IntegerField): + """ + An IntegerField where the field can be null if the input data is an empty + string. + """ + + def process_formdata(self, valuelist): + if valuelist: + if valuelist[0] == '' or valuelist[0] is None: + self.data = None + else: + try: + self.data = int(valuelist[0]) + except ValueError: + self.data = None + raise ValueError(self.gettext('Not a valid integer value')) + +class MachineqNodeForm(Form, UserForm): + """ A form for validating machineq node requests.""" + + id = wtf.IntegerField( + validators=[wtf.validators.Optional()] + ) + gateway_id = wtf.IntegerField( + validators=[wtf.validators.Required()] + ) + deveui = wtf.StringField( + validators=[wtf.validators.Optional()] + ) + space_id = NullableIntegerField( + validators=[wtf.validators.Optional()] + ) + node_type = NullableIntegerField( + validators=[wtf.validators.Optional()] + ) + surface_type = wtf.StringField( + validators=[wtf.validators.Optional()] + ) + notes = wtf.StringField( + validators=[wtf.validators.Optional()] + ) diff --git a/app/models/machineq_node.py b/app/models/machineq_node.py new file mode 100644 index 0000000000000000000000000000000000000000..994a1ffbe37842f9e8a357358a1c1ef4c595b7be --- /dev/null +++ b/app/models/machineq_node.py @@ -0,0 +1,19 @@ +from .base import Model, Tracked, User +from ..lib.database import db + +from sqlalchemy.ext.declarative import declared_attr + +class MachineqNode(Model, User, Tracked, db.Model): + + @declared_attr + def __tablename__(cls): + return "machineq_node" + + __table_args__ = {"schema": "sensor"} + + gateway_id = db.Column(db.Integer) + deveui = db.Column(db.Unicode(30)) + space_id = db.Column(db.Integer) + node_type = db.Column(db.Integer()) + surface_type = db.Column(db.Unicode(50)) + notes = db.Column(db.Unicode(500)) diff --git a/app/views/__init__.py b/app/views/__init__.py index a8534aea068d910d6a0867bc0b055496bf22e2a5..2636c8776a6f8d4875b6a7146434d178fae6b3df 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -2,6 +2,7 @@ from . import ( gateway, senseware_node, + machineq_node, data, sensor_image, event, @@ -17,6 +18,7 @@ def register(app): """ gateway.GatewayView.register(app) senseware_node.SensewareNodeView.register(app) + machineq_node.MachineqNodeView.register(app) data.DataView.register(app) sensor_image.SensorImageView.register(app) event.EventView.register(app) diff --git a/app/views/machineq_node.py b/app/views/machineq_node.py new file mode 100644 index 0000000000000000000000000000000000000000..b2d7d85fbd8b07df733aaa6656e79e6142335bc6 --- /dev/null +++ b/app/views/machineq_node.py @@ -0,0 +1,12 @@ +"""Views for working with machineq nodes.""" +from .base import RestView +from ..controllers.machineq_node import MachineqNodeController + +from flask import request + +class MachineqNodeView(RestView): + """The machineq node view.""" + + def get_controller(self): + """Return an instance of the machineq node controller.""" + return MachineqNodeController()