diff --git a/app/api.py b/app/api.py index a7a53a8..ceb9ba1 100644 --- a/app/api.py +++ b/app/api.py @@ -10,7 +10,10 @@ These functions are acting as very light controllers essentially. ''' def post_a_reading(light_meter, the_reading): - return post_services.add_latest_reading(light_meter,the_reading) + return post_services.add_latest_reading(light_meter, the_reading) + +def post_a_status_change(device, the_status_change): + return post_services.log_status_change(device, the_status_change) def get_latest(light_meter): return get_services.get_latest_reading(light_meter) diff --git a/app/build_database.py b/app/build_database.py index 059a07d..c88772e 100644 --- a/app/build_database.py +++ b/app/build_database.py @@ -2,6 +2,7 @@ import os from datetime import datetime from config import db from models.meters import Meter1, Meter2, Meter3 +from models.devices import Device1, Device2, Device3, Device4, Device5, Device6 def get_timestamp(): return datetime.now().strftime(("%Y-%m-%d %H:%M:%S")) @@ -10,6 +11,12 @@ def get_timestamp(): READINGS1 =[ {"time":datetime.now(), "reading": 0} ] READINGS2 =[ {"time":datetime.now(), "reading": 0} ] READINGS3 =[ {"time":datetime.now(), "reading": 0} ] +DEVICE1 = [ {"time":datetime.now(), "status": "off"} ] +DEVICE2 = [ {"time":datetime.now(), "status": "off"} ] +DEVICE3 = [ {"time":datetime.now(), "status": "off"} ] +DEVICE4 = [ {"time":datetime.now(), "status": "off"} ] +DEVICE5 = [ {"time":datetime.now(), "status": "off"} ] +DEVICE6 = [ {"time":datetime.now(), "status": "off"} ] # Deletes the database if it already exists if os.path.exists("readings.db"): @@ -28,9 +35,35 @@ for info in READINGS2: r = Meter2(time=info.get("time"), reading=info.get("reading")) db.session.add(r) -# Iterates over the READINGS3 structure and populates the datebase +# Iterates over the READINGS3 structure and populates the datebase. for info in READINGS3: r = Meter3(time=info.get("time"), reading=info.get("reading")) db.session.add(r) + +# These are the light-meters in Ritherdon. +for info in DEVICE1: + r = Device1(time=info.get("time"), status=info.get("status")) + db.session.add(r) + +for info in DEVICE2: + r = Device2(time=info.get("time"), status=info.get("status")) + db.session.add(r) + +for info in DEVICE3: + r = Device3(time=info.get("time"), status=info.get("status")) + db.session.add(r) + +# These are the relays in the gallery. +for info in DEVICE4: + r = Device4(time=info.get("time"), status=info.get("status")) + db.session.add(r) + +for info in DEVICE5: + r = Device5(time=info.get("time"), status=info.get("status")) + db.session.add(r) + +for info in DEVICE6: + r = Device6(time=info.get("time"), status=info.get("status")) + db.session.add(r) db.session.commit() diff --git a/app/models/devices.py b/app/models/devices.py new file mode 100644 index 0000000..25c2c35 --- /dev/null +++ b/app/models/devices.py @@ -0,0 +1,82 @@ +from datetime import datetime +from config import db, ma + +''' +Note on Duplication Levels +====================================================================== +While the code in this file seems very duplicated, it is just the +result of using SQL-Alchemy (ORM) and the repetitive nature of the +project as a whole. At the time of writing, the expected amount of +devices is six. They must keep their info. separate from each other. +This means a table in the database for each device. This is the main +cause for the repetitive/duplicated code. Because this project has +fixed requirements, the hard-coded nature is a trade-off because of +this. If the project increases the amount of devices it uses, this +will probably need to be refactored. +''' + +class Device1(db.Model): + __tablename__ = "device1" + id = db.Column(db.Integer, primary_key=True) + time = db.Column(db.DateTime, default=datetime.utcnow) + status = db.Column(db.String) + +class Device1Schema(ma.ModelSchema): + class Meta: + model = Device1 + sqla_session = db.session + +class Device2(db.Model): + __tablename__ = "device2" + id = db.Column(db.Integer, primary_key=True) + time = db.Column(db.DateTime, default=datetime.utcnow) + status = db.Column(db.String) + +class Device2Schema(ma.ModelSchema): + class Meta: + model = Device2 + sqla_session = db.session + +class Device3(db.Model): + __tablename__ = "device3" + id = db.Column(db.Integer, primary_key=True) + time = db.Column(db.DateTime, default=datetime.utcnow) + status = db.Column(db.String) + +class Device3Schema(ma.ModelSchema): + class Meta: + model = Device1 + sqla_session = db.session + +class Device4(db.Model): + __tablename__ = "device4" + id = db.Column(db.Integer, primary_key=True) + time = db.Column(db.DateTime, default=datetime.utcnow) + status = db.Column(db.String) + +class Device4Schema(ma.ModelSchema): + class Meta: + model = Device1 + sqla_session = db.session + +class Device5(db.Model): + __tablename__ = "device5" + id = db.Column(db.Integer, primary_key=True) + time = db.Column(db.DateTime, default=datetime.utcnow) + status = db.Column(db.String) + +class Device5Schema(ma.ModelSchema): + class Meta: + model = Device1 + sqla_session = db.session + +class Device6(db.Model): + __tablename__ = "device6" + id = db.Column(db.Integer, primary_key=True) + time = db.Column(db.DateTime, default=datetime.utcnow) + status = db.Column(db.String) + +class Device6Schema(ma.ModelSchema): + class Meta: + model = Device1 + sqla_session = db.session diff --git a/app/readings.db b/app/readings.db index 4cb822b..b555a97 100644 Binary files a/app/readings.db and b/app/readings.db differ diff --git a/app/readings.db-journal b/app/readings.db-journal new file mode 100644 index 0000000..290d5f9 Binary files /dev/null and b/app/readings.db-journal differ diff --git a/app/services/get_services.py b/app/services/get_services.py index 1b154b9..ec43e87 100644 --- a/app/services/get_services.py +++ b/app/services/get_services.py @@ -12,6 +12,8 @@ at Ritherdon, you will need to head to the /post_services.py/ file. It should be in the same directory at this: /services/. ''' +bad_meter_id_message = "Meter Id. not recognised. Must be between 1 and 3." + def get_latest_reading(meter): if meter == 1: return get_m1_latest() @@ -19,6 +21,7 @@ def get_latest_reading(meter): return get_m2_latest() elif meter == 3: return get_m3_latest() + return make_response(bad_meter_id_message, 400) def get_all_readings_from_table(name): if name == 1: @@ -27,6 +30,7 @@ def get_all_readings_from_table(name): return get_all_readings_for_meter2() elif name == 3: return get_all_readings_for_meter3() + return make_response(bad_meter_id_message, 400) def get_all_readings_from_database(): return get_all_readings() diff --git a/app/services/post_services.py b/app/services/post_services.py index 9315d9a..6617b1f 100644 --- a/app/services/post_services.py +++ b/app/services/post_services.py @@ -2,14 +2,18 @@ from flask import make_response, abort from config import db from models.meters import (Meter1, Meter1Schema, Meter2, Meter2Schema, Meter3, Meter3Schema) +from models.devices import (Device1, Device1Schema, Device2, Device2Schema, + Device3, Device3Schema, Device4, Device4Schema, + Device5, Device5Schema, Device6, Device6Schema) ''' Post Services Note ====================================================================== The functions in this file are for storing the readings taken from the -light meters in Ritherdon. It you are wanting to retrieve data from -the (this) server, you will need to head to the /get_services.py/ file. -It should be in the same directory as this: /services/. +light meters in Ritherdon, and log changes in state for any of the +devices. It you are wanting to retrieve data from the (this) server, +you will need to head to the /get_services.py/ file. It should be in +the same directory as this: /services/. ''' def add_latest_reading(meter,reading): @@ -19,24 +23,96 @@ def add_latest_reading(meter,reading): return add_reading_to_meter2(reading) elif meter == 3: return add_reading_to_meter3(reading) - + return make_response("Meter Id. not recognised. Must be between 1 and 3.", 400) + +def log_status_change(device, status): + if device == 1: + return add_status_change_to_device1(status) + elif device == 2: + return add_status_change_to_device2(status) + elif device == 3: + return add_status_change_to_device3(status) + elif device == 4: + return add_status_change_to_device4(status) + elif device == 5: + return add_status_change_to_device5(status) + elif device == 6: + return add_status_change_to_device6(status) + return make_response("Device Id. not recognised. Must be between 1 and 6.", 400) + +''' +Nitty-Gritty Functions +====================================================================== +The functions above are basically "header" functions. It makes it +easier to have multiple files open at once and see what this module +provides function-wise. The functions below do the real work in this +file. Please keep the "public" functions about this comment and defer +the main work to here. The "public" functions should be as thin as +possible to make them as scanable as possible. +''' + +reading_message = "Reading successfully stored in database." +status_message = "Status change successfully logged in database." + def add_reading_to_meter1(the_reading): schema = Meter1Schema() new_reading = schema.load(the_reading, session=db.session) db.session.add(new_reading) db.session.commit() - return make_response("Reading successfully stored in database.", 201) + return make_response(reading_message, 201) def add_reading_to_meter2(the_reading): schema = Meter2Schema() new_reading = schema.load(the_reading, session=db.session) db.session.add(new_reading) db.session.commit() - return make_response("Reading successfully stored database.", 201) + return make_response(reading_message, 201) def add_reading_to_meter3(the_reading): schema = Meter3Schema() new_reading = schema.load(the_reading, session=db.session) db.session.add(new_reading) db.session.commit() - return make_response("Reading successfully stored database.", 201) + return make_response(reading_message, 201) + +def add_status_change_to_device1(status): + schema = Device1Schema() + new_status = schema.load(status, session=db.session) + db.session.add(new_status) + db.session.commit() + return make_response(status_message, 201) + +def add_status_change_to_device2(status): + schema = Device2Schema() + new_status = schema.load(status, session=db.session) + db.session.add(new_status) + db.session.commit() + return make_response(status_message, 201) + +def add_status_change_to_device3(status): + schema = Device3Schema() + new_status = schema.load(status, session=db.session) + db.session.add(new_status) + db.session.commit() + return make_response(status_message, 201) + +def add_status_change_to_device4(status): + schema = Device4Schema() + new_status = schema.load(status, session=db.session) + db.session.add(new_status) + db.session.commit() + return make_response(status_message, 201) + +def add_status_change_to_device5(status): + schema = Device5Schema() + new_status = schema.load(status, session=db.session) + db.session.add(new_status) + db.session.commit() + return make_response(status_message, 201) + +def add_status_change_to_device6(status): + schema = Device6Schema() + new_status = schema.load(status, session=db.session) + db.session.add(new_status) + db.session.commit() + return make_response(status_message, 201) diff --git a/app/swagger.yml b/app/swagger.yml index 804f29b..205c78a 100644 --- a/app/swagger.yml +++ b/app/swagger.yml @@ -16,7 +16,7 @@ paths: post: operationId: api.post_a_reading tags: - - Store Readings + - Log Light-Meter Readings summary: >- Posts a reading from one of the project's light meters and store it in the database. @@ -54,7 +54,7 @@ paths: time: type: string format: date-time - example: 2019-10-19 17:04:57.880010 + example: 2019-10-19 17:04:57 description: >- The date and time the reading was taken. Make sure you use the UTC time format. This is because the @@ -72,11 +72,60 @@ paths: description: >- The reading was successfully added to the database. + /status/update/{device}: + post: + operationId: api.post_a_status_change + tags: + - Log Device Status Change + summary: >- + Logs a change in the status of the specified device. + description: >- + This is mostly to check if the devices included in this project + are turned on, off or an error has occured and they are + unreachable/crashed. + parameters: + - name: device + in: path + description: >- + The Id. of the device with the change in status. 1, 2 and 3 + refer to the light-meters in Ritherdon and 4, 5 and 6 + refer to the relays in the gallery. The pairing of each + meter and relay is as follows: 1->4, 2->5 and 3->6. + type: integer + required: True + - name: the_status_change + in: body + description: >- + The status change and the time the change occurred. + required: True + schema: + type: object + properties: + time: + type: string + format: date-time + example: 2019-10-19 17:04:57 + description: >- + The date and time the change in status occurred. Make + sure to use the U.T.C. time format. This is because + the A.P.I. has made a design decision to standardise + on it. + status: + type: string + example: "on" + description: >- + The current status of the device you would like to log. + The two status types are "on" and "off". + responses: + 201: + description: >- + The status update was successfully added to the database. + /readings/latest/{light_meter}: get: operationId: api.get_latest tags: - - Request Readings + - Request Light-Meter Readings summary: >- Returns the latest reading from the specified light meter. description: >- @@ -129,7 +178,7 @@ paths: get: operationId: api.get_all_readings tags: - - Request Readings + - Request Light-Meter Readings summary: >- Returns every reading taken by the specified light meter. description: >- @@ -180,7 +229,7 @@ paths: get: operationId: api.get_all_readings_for_every_meter tags: - - Request Readings + - Request Light-Meter Readings summary: >- Returns every reading taken by every light meter in the project.