summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api.py30
-rw-r--r--data.dbbin0 -> 36864 bytes
-rw-r--r--errors.py10
-rw-r--r--heating.py85
-rw-r--r--main.py85
-rw-r--r--sql.py121
-rw-r--r--weather.py40
7 files changed, 264 insertions, 107 deletions
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
--- /dev/null
+++ b/data.db
Binary files 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/<opt>')
+@app.route("/export/<opt>")
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