diff --git a/db_connect.py b/db_connect.py
index fa751ab..66b47af 100644
--- a/db_connect.py
+++ b/db_connect.py
@@ -4,6 +4,8 @@ import logging
from typing import Generator
import datetime
+from templating import SensorInfo
+
# main database connector class
# permission validation is not performed on this class
# perform those higher on the call stack
@@ -36,10 +38,14 @@ class DatabaseConnect:
def display_users(self) -> tuple[tuple[int, str, int]]:
self.cursor.execute("SELECT * FROM Users")
return self.cursor.fetchall()
+
+ def username_from_id(self, user_id):
+ self.cursor.execute("SELECT users.`username` FROM users WHERE `ID` = 1;")
+ return self.cursor.fetchone()
- def view_valid_rooms(self, user_id) -> list[tuple[str, str]]:
+ def view_valid_rooms(self, user_id) -> list[tuple[str, str, int]]:
self.cursor.execute(
- "SELECT rooms.name, rooms.shortname from permissions INNER JOIN rooms ON permissions.`roomID` = rooms.`ID` WHERE permissions.`userID` = ? AND permissions.`view` = 1;",
+ "SELECT rooms.name, rooms.shortname, rooms.ID from permissions INNER JOIN rooms ON permissions.`roomID` = rooms.`ID` WHERE permissions.`userID` = ? AND permissions.`view` = 1;",
(user_id,))
return self.cursor.fetchall()
@@ -55,9 +61,22 @@ class DatabaseConnect:
# self.cursor.execute("SELECT rooms.`ID` from rooms WHERE rooms.shortname = ?;",(shortname,))
# return self.cursor.fetchone()
- def get_sensors_in_room(self, shortname) -> list[int]:
- self.cursor.execute("SELECT devices.`ID` from devices LEFT JOIN rooms ON devices.`roomID` = rooms.ID WHERE rooms.shortname = ?;",(shortname,))
+ def get_sensors_in_room_shortname(self, shortname) -> list[int]:
+ self.cursor.execute("SELECT Sensors.`ID` from Sensors LEFT JOIN rooms ON Sensors.`roomID` = rooms.ID WHERE rooms.shortname = ?;",(shortname,))
return [x[0] for x in self.cursor.fetchall()]
+
+ def get_sensors_in_room_id(self, roomid) -> list[tuple[int, int]]:
+ self.cursor.execute("SELECT Sensors.`ID`, Sensors.`Type` from Sensors LEFT JOIN rooms ON Sensors.`roomID` = rooms.ID WHERE rooms.`ID` = ?;",(roomid,))
+ return [(x[0], x[1]) for x in self.cursor.fetchall()]
+
+ def get_sensorsinfo_by_sensorid(self, sensorid) -> SensorInfo | None:
+ self.cursor.execute("SELECT readings.`Timestamp`, readings.`reading` FROM readings WHERE readings.`sensorID` = ? ORDER BY readings.`Timestamp` DESC FETCH FIRST 1 ROWS ONLY;",(sensorid,))
+ fetch = self.cursor.fetchone()
+ if fetch is None:
+ return None
+ else:
+ return SensorInfo(0,sensorid, fetch[0], fetch[1])
+
def get_sensor_data(self, sensor_ID: int) -> Generator[tuple[datetime.datetime, float]]:
self.cursor.execute("SELECT Timestamp, reading FROM Readings WHERE `sensorID` = ? ORDER BY `Timestamp` DESC;",(sensor_ID,))
@@ -65,7 +84,7 @@ class DatabaseConnect:
yield row
def get_sensor_type(self, sensor_ID):
- self.cursor.execute("SELECT types.`type_desc` from types LEFT JOIN devices ON types.`ID` = devices.`type` WHERE devices.`ID` = ?;",(sensor_ID,))
+ self.cursor.execute("SELECT types.`type_desc` from types LEFT JOIN Sensors ON types.`ID` = Sensors.`type` WHERE Sensors.`ID` = ?;",(sensor_ID,))
return self.cursor.fetchone()[0]
if __name__ == "__main__":
diff --git a/sql_startup.sql b/sql_startup.sql
index 44d187b..b6056f9 100644
--- a/sql_startup.sql
+++ b/sql_startup.sql
@@ -9,13 +9,18 @@ INSERT INTO Users (`ID`, `username`, `pwd`) VALUES (1,'nouser',NULL), (NULL,'Adm
CREATE TABLE Permissions (`userID` INT, `roomID` INT, `view` BOOLEAN DEFAULT 0, `purge_data` BOOLEAN DEFAULT 0, `administer` BOOLEAN DEFAULT 0);
CREATE TABLE Rooms (`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, `name` TEXT, `shortname` TEXT UNIQUE);
-CREATE TABLE Devices(`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, `type` INT, `roomID` INT, `mqttTopic` TEXT);
+CREATE TABLE Sensors(`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, `type` INT, `roomID` INT, `mqttTopic` TEXT);
CREATE TABLE Types(`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, `type_desc` TEXT);
+
+CREATE TABLE Devices(`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, `roomID` INT, `name` TEXT, `description` TEXT);
+
CREATE TABLE Readings(`sensorID` INT, `Timestamp` DATETIME, `reading` DOUBLE);
CREATE INDEX `sensor_index` ON Readings (`sensorID`);
+CREATE TABLE Log( `timestamp` TIMESTAMP, `type` INT, `message` TEXT);
+
INSERT INTO Rooms (`name`,`shortname`) VALUES ('101','101'),('102','102'),('210','210'),('211','211'),('215 - Studovna','215');
-INSERT INTO Devices (`type`, `roomID`,`mqttTopic`) VALUES (1,1,"101/floortemp"),(1,1,"101/ceiltemp"),(2,1,"101/humidity"),(1,3,"210/temp"),(1,5,"215/temp"),(2,5,"215/humidity");
+INSERT INTO Sensors (`type`, `roomID`,`mqttTopic`) VALUES (0,1,"101/floortemp"),(0,1,"101/ceiltemp"),(1,1,"101/humidity"),(0,3,"210/temp"),(0,5,"215/temp"),(1,5,"215/humidity");
INSERT INTO Types (type_desc) VALUES ("Temperature"), ('Humidity');
INSERT INTO `readings`(`Timestamp`,`reading`,`sensorID`) VALUES
('2025-11-12 00:00:00',25.7,1),('2025-11-12 01:00:00',26.7,1),('2025-11-12 02:00:00',27.4,1),('2025-11-12 02:04:00',28.0,1),('2025-11-12 03:22:00',28.2,1),
@@ -31,7 +36,10 @@ SELECT rooms.name, rooms.shortname from permissions INNER JOIN rooms ON permissi
SELECT rooms.`ID` from rooms WHERE rooms.shortname = '101';
-SELECT devices.`ID` from devices LEFT JOIN rooms ON devices.`roomID` = rooms.ID WHERE rooms.shortname = '101';
-SELECT types.`type_desc` from types LEFT JOIN devices ON types.`ID` = devices.`type` WHERE devices.`ID` = 1;
+SELECT Sensors.`ID` from Sensors LEFT JOIN rooms ON Sensors.`roomID` = rooms.ID WHERE rooms.shortname = '101';
+SELECT types.`type_desc` from types LEFT JOIN Sensors ON types.`ID` = Sensors.`type` WHERE Sensors.`ID` = 1;
-SELECT permissions.`view` FROM permissions LEFT JOIN rooms ON permissions.`roomID` = rooms.ID WHERE rooms.shortname = '101' AND `userID` = 1;
\ No newline at end of file
+SELECT permissions.`view` FROM permissions LEFT JOIN rooms ON permissions.`roomID` = rooms.ID WHERE rooms.shortname = '101' AND `userID` = 1;
+
+# get latest reading from a sensor
+SELECT readings.`Timestamp`, readings.`reading` FROM readings WHERE readings.`sensorID` = '1' ORDER BY readings.`Timestamp` DESC FETCH FIRST 1 ROWS ONLY;
diff --git a/static/scripts.js b/static/scripts.js
new file mode 100644
index 0000000..4182906
--- /dev/null
+++ b/static/scripts.js
@@ -0,0 +1,19 @@
+function tableSearch(){
+ var table, input, tr, td, row_content;
+ table = document.getElementById("tableRooms");
+ input = document.getElementById("tableRoomSearch").value.toUpperCase();
+ tr = table.getElementsByTagName("tr");
+
+ for (let i = 0; i < tr.length; i++) {
+ td = tr[i].getElementsByTagName("td")[0];
+ if(td){
+ row_content = td.textContent || td.innerText;
+ if (row_content.toUpperCase().indexOf(input) > -1) {
+ tr[i].style.display = "";
+ }
+ else{
+ tr[i].style.display ="none";
+ }
+ }
+ }
+ }
\ No newline at end of file
diff --git a/web/styles.css b/static/styles.css
similarity index 94%
rename from web/styles.css
rename to static/styles.css
index cde5206..c7003c0 100644
--- a/web/styles.css
+++ b/static/styles.css
@@ -6,7 +6,7 @@ a{
text-decoration: none;
}
-.main{
+.main-flex{
display: flex;
flex-direction: row;
}
@@ -29,7 +29,7 @@ a{
font-weight: normal;
}
-.content{
+.right-flex{
flex: 1 60vw;
}
@@ -43,7 +43,7 @@ a{
text-align: right;
}
-.main-content{
+.content{
padding-left: 1em;
}
@@ -75,10 +75,11 @@ thead{
tbody tr{
border-bottom: 0.1em darkgray solid;
}
-.missing-data{
+.data-missing{
background-color: gray;
+ color: transparent
}
-.late-data{
+.data-late{
background-color: orange;
}
.room-table-elem, .searchbar{
diff --git a/static_text.toml b/static_text.toml
new file mode 100644
index 0000000..7303fbc
--- /dev/null
+++ b/static_text.toml
@@ -0,0 +1,21 @@
+# Definitions for various pieces of static text
+# Raw HTML may be inserted for further formatting purposes
+
+pagetitle = "Room air quality Example Inc."
+
+# used in the top left corner for identifying the website
+# logos and links to the main pages are good ideas to insert here
+top_branding = "Example Inc. Room air quality monitoring"
+
+
+# user greeting after they log in, this is the part that goes
+# before the name itself
+user_greeting_before = "Welcome back "
+
+# followed by the part after the username
+user_greeting_after= "!"
+
+# landing information also displayed on the main page of a logged in user
+# or a non logged in user if allowed
+# this is typically a paragraph introducing the nature of the service to the user
+landing_information = "This service can be used to monitor the air quality in most rooms of the Example Inc. headquarters so that you and your coworkers may know the room is suitable for use or may prepare the room before using it."
diff --git a/templating.py b/templating.py
new file mode 100644
index 0000000..a99ef55
--- /dev/null
+++ b/templating.py
@@ -0,0 +1,49 @@
+from flask import url_for, Flask
+from jinja2 import FileSystemLoader
+
+import tomllib
+from os import sep
+from collections import namedtuple
+
+def static_test_load():
+ with open("static_text.toml", "rb") as f:
+ return tomllib.load(f)
+
+UserInfo = namedtuple('UserInfo',['id', 'name'])
+RoomInfo = namedtuple('RoomInfo',['name', 'shortcode', 'roomdata'])
+# state is an int not in db
+# 0 - value valid
+# 1 - value late
+# 2 - value missing
+SensorInfo = namedtuple('SensorInfo',['state','type','timestamp','reading'])
+
+# base class for inheriting other more specific pages
+class BasePage():
+ def __init__(
+ self,
+ jijna_env,
+ target_path = "base.html.jinja" ,
+ statictext = None,
+ ) -> None:
+ self.env = jijna_env
+ self.target = target_path
+ self.rendervars = static_test_load() if statictext is None else statictext
+
+ def render(self):
+ url_for('static', filename="styles.css")
+ url_for('static', filename="scripts.css")
+ template = self.env.get_template(self.target)
+ return template.render(self.rendervars)
+
+class LandingPage(BasePage):
+ def __init__(
+ self,
+ jinja_env,
+ userinfo,
+ roominfo,
+ target_path="landing.html.jinja",
+ statictext=None,
+ ) -> None:
+ super().__init__(jinja_env, target_path, statictext)
+ self.rendervars["userinfo"] = userinfo
+ self.rendervars["roominfo"] = roominfo
diff --git a/test.py b/test.py
new file mode 100644
index 0000000..6dbed81
--- /dev/null
+++ b/test.py
@@ -0,0 +1,32 @@
+from jinja2 import Template
+
+text = """
+
+
+
+
+