From f315adea703193deb7419355dc084e903dbfde1e Mon Sep 17 00:00:00 2001 From: root Date: Sat, 15 Jan 2022 14:46:26 +0000 Subject: Development --- api.py | 30 +++++++++++++++ data.db | Bin 0 -> 36864 bytes errors.py | 10 +++++ heating.py | 85 +++++++++++++++++++++++++++++++++---------- main.py | 85 +++++++++++++++++++++++++++---------------- sql.py | 121 ++++++++++++++++++++++++++++++++++--------------------------- weather.py | 40 +++++++++++++++++++- 7 files changed, 264 insertions(+), 107 deletions(-) create mode 100644 api.py create mode 100644 data.db create mode 100644 errors.py diff --git a/api.py b/api.py new file mode 100644 index 0000000..95dd72b --- /dev/null +++ b/api.py @@ -0,0 +1,30 @@ +@app.route("/api/info") +def api_info(): + msg = """ + You can access data with the api. There are multiple api routes to + get data. They are all in the format of [ip address]/api/[data] + + [ip] is used for the device ip address + + [ip]/api/current_temperautre + returns the current actual temperature + [ip]/api/target_temperature + returns the current target temperature + [ip]/api/on + returns True if the heating is off and False if it is off + """ + + +@app.route("/api/current_temperature") +def api_current_temperature(): + return boiler.temperature + + +@app.route("/api/target_temperature") +def api_target_temperature(): + return boiler.target + + +@app.route("/api/heating_on") +def api_on(): + return boiler.on diff --git a/data.db b/data.db new file mode 100644 index 0000000..466c0ce Binary files /dev/null and b/data.db differ diff --git a/errors.py b/errors.py new file mode 100644 index 0000000..5e7394b --- /dev/null +++ b/errors.py @@ -0,0 +1,10 @@ +def device_error(): + print( + """ + The device you are running on is not currently compatible. + Please use a suitable device. The current suitable + devices are: + - Raspberry Pi 4 + """ + ) + quit() diff --git a/heating.py b/heating.py index e780cd2..a774254 100644 --- a/heating.py +++ b/heating.py @@ -1,14 +1,14 @@ -import RPi.GPIO as GPIO from abc import ABC, abstractmethod +import time +import os # Abstract Class for heating class heating(ABC): def __init__(self, db): self.temperature = 0 - self.taget = 0 + self.target = 19 self.on = False self.db = db - self.setup() def up(self, increase=0.5): self.target += increase @@ -19,28 +19,38 @@ class heating(ABC): self.update() def update(self): + self.temperature = self.get_temperature() if self.temperature < self.target: self.on = True else: self.on = False - ''' + ''' + self.db.exec( + "insert into temperature values (?,?,?,?)", + (date, time, self.temperature, self.target), + ) + self.db.exec( + "insert into history values (?,?,?)", + (date, time, heatingon(int)), + ) + ''' + + """ Abstract methods used so that configuration for different device types other than a Raspberry Pi is easier in the future and all other methods can simply be inherited - ''' + """ - # Method for initial setup according to device GPIO - @abstractmethod - def setup(self): - pass # Method to turn the heating on @abstractmethod def turn_on(self): pass + # Method to turn the heating off @abstractmethod def turn_off(self): pass + # Method to get the current actual temperature, can be changed for # different types of temperature sensor when inheriting this # class @@ -48,19 +58,54 @@ class heating(ABC): def get_temperature(self): pass -# inherrited from heating class + +# inherited from heating class # overriding abstract methods from heating class class rpi_heating(heating): - def setup(self): - # GPIO Numbers instead of board numbers - GPIO.setmode(GPIO.BCM) + def __init__(self,db): # https://tutorials-raspberrypi.com/raspberry-pi-control-relay-switch-via-gpio/ - # constant value for the relay number - self.RELAY = 17 - GPIO.setup(self.RELAY, GPIO.OUT) # GPIO Assign mode - def turn_on(self): - GPIO.output(self.RELAY, GPIO.LOW) # out + # https://www.ics.com/blog/gpio-programming-using-sysfs-interface + # https://medium.com/initial-state/how-to-build-a-raspberry-pi-temperature-monitor-8c2f70acaea9 + + # run init from super class + super().__init__(db) + + # constant values + self.RELAY = 17 # relay GPIO pin + self.SENSORID = "28-0621412f1b5a" # serial number of sensor + + # setup GPIO for the relay + + os.system( + "echo {} >/sys/class/gpio/export".format(self.RELAY) + ) + os.system( + "echo out >/sys/class/gpio/gpio{}/direction".format( + self.RELAY + ) + ) + # file that the temperature gets written t + self.sensor = "/sys/bus/w1/devices/{}/w1_slave".format( + self.SENSORID + ) + def turn_on(self): - GPIO.output(self.RELAY, GPIO.HIGH) # on + # turn the heating on by triggering the relay + os.system( + "echo 1 >/sys/class/gpio/gpio{}/value".format(self.RELAY) + ) + + def turn_off(self): + # turn the heating off by triggering the relay + os.system( + "echo 0 >/sys/class/gpio/gpio{}/value".format(self.RELAY) + ) + def get_temperature(self): - pass + with open(self.sensor, "r") as file: + data = file.read() + # parse temperature from file + return ( + float(data.split("\n")[1].split(" ")[9].split("=")[1]) + / 1000 + ) diff --git a/main.py b/main.py index c642d17..daae78f 100644 --- a/main.py +++ b/main.py @@ -1,56 +1,79 @@ -import sqlite3 +# Import other functions and classes import heating +import errors import sql -import time +import weather from flask import Flask, render_template app = Flask(__name__) -boiler = heating.rpi_heating() +# Paramaterised location of database file +# Use of constant +DBFILE = "data.db" -# paramaterised location of database file -# Use of final data type -global DBFILE -DBFILE = 'data.db' +db = sql.db(DBFILE) +#weather = weather_met_no() -db = sql.db(dbfile) +# Check if running on correct device : defensive coding against running +# on the wrong device +try: + with open("/sys/firmware/devicetree/base/model", "r") as file: + device = file.read() +except: + errors.device_error() -@app.route('/') -@app.route('/index.html') -def index(): +if device.startswith("Raspberry Pi 4"): + boiler = heating.rpi_heating(db) +else: + errors.device_error() + + +def main_page(): # paramaterised location of template in 'templates' folder return render_template( - "main.html", - actual_temp = boiler.temp, - target_temp = boiler.target - ) + "main.html", + actual_temp=boiler.temperature, + target_temp=boiler.target + #forecast_temp=weather.temperature() + ) + + +@app.route("/") +@app.route("/index.html") +def index(): + return main_page() -@app.route('/up') + +@app.route("/up") def form(): boiler.up() - return render_template("main.html") + return main_page() + -@app.route('/down') +@app.route("/down") def activity(): - boilder.down() - return render_template("main.html") + boiler.down() + return main_page() + + +@app.route("/export") +def export_choice(): + return render_template("export_choice.html") + -@app.route('/export/') +@app.route("/export/") def export(opt): - if opt == 'txt': + if opt == "txt": pass - elif opt == 'pdf': + elif opt == "pdf": pass else: - return render_template('error.html', error="Invalid export type") + return render_template( + "error.html", error="Invalid export type" + ) -if __name__ == '__main__': - # if the database file does not exist, create it - try: - open(dbfile) - except: - sql.setup(dbfile) - app.run() +if __name__ == "__main__": + app.run(host="0.0.0.0") diff --git a/sql.py b/sql.py index b3db165..d9704d6 100644 --- a/sql.py +++ b/sql.py @@ -1,6 +1,8 @@ import sqlite3 + class db: + # https://docs.python.org/3/library/sqlite3.html def __init__(self, dbfile): # final variable self.dbfile = dbfile @@ -8,64 +10,75 @@ class db: open(dbfile) except FileNotFoundError: self.setup() - def exec(cmd): + + def exec(cmd, param=None): + """ + A function to execute a database command without having to + create the cursor + """ con = sqlite3.connect(self.dbfile) cur = con.cursor - cur.execute(cmd) + if param == None: + cur.execute(cmd) + else: + cur.execute(cmd, param) + con.commit() + con.close() + + def setup(self): + """ + Necessary when the database does not exist and the tables must be + created for the first time, otherwise not needed to be run. + """ + con = sqlite3.connect(self.dbfile) + cur = con.cursor() + cur.execute( + """ + CREATE TABLE temperature ( + date text not null, + time integer not null, + temperature real, + target real, + PRIMARY KEY (date, time) + ); + """ + ) + cur.execute( + """ + CREATE TABLE weather ( + date text not null, + temperature real, + wind real, + PRIMARY KEY (date), + FOREIGN KEY (date) REFERENCES temperature(date) + ); + """ + ) + cur.execute( + """ + CREATE TABLE schedule ( + day text not null, + time integer not null, + temperature real, + PRIMARY KEY (day, time) + ); + """ + ) + cur.execute( + """ + CREATE TABLE history ( + date text not null, + time integer not null, + heating_on integer, + PRIMARY KEY (date, time), + FOREIGN KEY (date) REFERENCES temperature(date), + FOREIGN KEY (time) REFERENCES temperature(time) + ); + """ + ) con.commit() con.close() + pass -def setup(dbfile): - ''' - Necessary when the database does not exist and the tables must be - created for the first time, otherwise not needed to be run. - ''' - con = sqlite3.connect(dbfile) - cur = con.cursor() - cur.execute( - ''' - CREATE TABLE temperature ( - date text not null, - time integer not null, - temperature real, - target real, - PRIMARY KEY (date, time) - ); - ''' - ) - cur.execute( - ''' - CREATE TABLE weather ( - date text not null, - temperature real, - wind real, - PRIMARY KEY (date), - FOREIGN KEY (date) REFERENCES temperature(date) - ); - ''' - ) - cur.execute( - ''' - CREATE TABLE schedule ( - day text not null, - time integer not null, - temperature real, - PRIMARY KEY (day, time) - ''' - ); - cur.execute( - ''' - CREATE TABLE history ( - date text not null, - time integer not null, - heating_on integer, - PRIMARY KEY (day, time), - FOREIGN KEY (date) REFERENCES temperature(date), - FOREIGN KEY (time) REFERENCES temperature(time) - ''' - ); - con.commit() - con.close() - pass def export_csv(self): return 0 diff --git a/weather.py b/weather.py index e46fac8..f33cbac 100644 --- a/weather.py +++ b/weather.py @@ -1,9 +1,45 @@ -class weather: +from abc import ABC, abstractmethod +import requests + +# Abstract class for weather +class weather(ABC): def __init__(self): self.temp = 0 + + @abstractmethod def get_temp(self): - #update temp + # update temp pass + def temperature(self): self.get_temp() return self.temp + + +# inheriting weather class +class weather_met_no(weather): + def __init__(self): + # constant for location in (latitude, longitude form + self.LOCATION = (51.52866, -0.35508) + # constant for api request when getting data + # uses Norweigen Meterological Institute api + # -- todo : reference + self.CALL = "https://api.met.no/weatherapi/locationforecast/2.0/compact?lat={}&lon={}".format( + self.LOCATION[0], self.LOCATION[1] + ) + # acceptable header for the API + # https://stackoverflow.com/questions/10606133/sending-user-agent-using-requests-library-in-python + # https://api.met.no/doc/FAQ + self.HEADERS = {"User-Agent": "TopologicalMap"} + super.init(self) + + def get_temperature(self): + # https://docs.python-requests.org/en/latest/ + try: + data = requests.get( + self.CALL, headers=self.HEADERS + ).json()['properties']['timeseries'][0]['data'] + return data['instant']['details']['air_temperature'] + # data['next_1_hours']['summary']['symbol_code'] + except: + return -1 -- cgit v1.2.3