all repos — iot-project @ 0627d8a0865451db9600af9ebcb04e083c391149

add ui, refactor code
Andronaco Marco marco.andronaco@olivetti.com
Thu, 21 Sep 2023 15:37:38 +0200
commit

0627d8a0865451db9600af9ebcb04e083c391149

parent

9cb1b83aa400107aaf241b15556d96932301c330

A backend_iot/common.py

@@ -0,0 +1,2 @@

+editable_fields = ["t_target", "power", "fan_speed", "auto", "airflow"] +current_latitude, current_longitude = 45.46437891252755, 7.872049153560152
M backend_iot/db.pybackend_iot/db.py

@@ -1,5 +1,7 @@

from peewee import Model, IntegerField, DateTimeField, DoesNotExist, FloatField, BooleanField from playhouse.sqliteq import SqliteQueueDatabase +from playhouse.shortcuts import model_to_dict +from backend_iot.common import current_latitude, current_longitude from backend_iot.api import get_aqi_owm from datetime import datetime, timedelta

@@ -70,12 +72,37 @@

db.connect() db.create_tables([Record, AQI]) -def getLatestRecord(): - #User.select().order_by(User.id.desc()).get() +def get_latest_record(): try: return Record.select().order_by(Record.timestamp.desc()).get() except DoesNotExist: - return addRecord() + return add_record() + +def add_record(**info): + return Record.create(**info) + +def update_record(record): + record["latitude"] = current_latitude + record["longitude"] = current_longitude + record["timestamp"] = datetime.now() + record["aqi"] = get_aqi(current_latitude, current_longitude) + return record + +def record_to_dict(record): + temp = model_to_dict(record) + del temp["id"] + return temp + +def handle_new_data(form): + latest = record_to_dict(get_latest_record()) + + for key in form.keys(): + if key not in latest.keys(): + continue + if key == "fan_speed" and latest["auto"]: + continue + latest[key] = form[key] -def addRecord(**info): - return Record.create(**info)+ new_record = update_record(latest) + add_record(**new_record) + return new_record
A backend_iot/static/globals.css

@@ -0,0 +1,21 @@

+@import url("https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css"); +* { + -webkit-font-smoothing: antialiased; + box-sizing: border-box; +} +html, +body { + margin: 0px; + height: 100%; + + color: white; +} +/* a blue color as a generic focus style */ +button:focus-visible { + outline: 2px solid #4a90e2 !important; + outline: -webkit-focus-ring-color auto 5px !important; +} +a { + text-decoration: none; +} +@import url("https://fonts.googleapis.com/css?family=Inter:400");
A backend_iot/static/img/tasto-ricircolo.svg

@@ -0,0 +1,3 @@

+<svg width="161" height="127" viewBox="0 0 161 127" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M0 35V92C0 111.33 15.67 127 35 127H161V0H35C15.67 0 0 15.67 0 35Z" fill="#D9D9D9"/> +</svg>
A backend_iot/static/img/vector-1.svg

@@ -0,0 +1,3 @@

+<svg width="117" height="52" viewBox="0 0 117 52" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M106.004 17.751H97.5009L82.3208 0H48.5545L30.4891 17.751H10.9902C4.92159 17.751 0 22.8618 0 29.1731V31.3469C0 37.6582 4.92159 42.769 10.9902 42.769H15.11C16.4677 48.0722 21.2781 52 27.0189 52C32.7715 52 37.5819 48.0722 38.9337 42.769H79.8922C81.244 48.0722 86.0602 52 91.8011 52C97.5536 52 102.358 48.0722 103.716 42.769H106.004C112.073 42.769 117 37.6582 117 31.3469V29.1731C117 22.8618 112.073 17.751 106.004 17.751ZM75.5148 35.129V32.8038H50.4505C49.9941 32.8038 47.2319 32.7047 44.9496 30.4669C43.0418 28.602 42.0821 25.8922 42.0821 22.4073C42.0821 18.8699 43.0828 16.0784 45.0666 14.1145C47.7 11.5038 50.9713 11.4222 51.3693 11.4455L81.2323 11.4338V16.3582L51.3576 16.3757C50.3569 16.399 47.027 16.906 47.027 22.4073C47.027 27.4074 49.6839 27.8619 50.4915 27.8794H75.5148V25.8572L88.7463 30.4902L75.5148 35.129Z" fill="black"/> +</svg>
A backend_iot/static/img/vector.svg

@@ -0,0 +1,3 @@

+<svg width="49" height="49" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M35.8996 5.39778C27.6978 5.15474 26.0768 12.4753 25.921 17.5827C25.4944 17.5017 25.0529 17.4575 24.5966 17.4575C23.788 17.4575 23.0201 17.5937 22.293 17.8405C21.7255 17.2955 21.4547 16.5774 21.3842 15.7047C21.1579 13.186 25.1531 11.4626 25.2644 7.78763C25.3942 3.95798 23.5172 0.993682 18.884 0.1762C14.6737 -0.563954 5.80416 0.548115 5.43692 13.0055C5.1958 21.1472 12.5703 22.7638 17.7191 22.9074C17.6041 23.4082 17.5337 23.9274 17.5337 24.465C17.5337 25.3083 17.6894 26.111 17.9751 26.8622C17.4186 27.4256 16.6953 27.6908 15.8125 27.7718C13.2826 28.0001 11.5465 24.0268 7.83698 23.909C3.98648 23.7875 1.00025 25.6581 0.17673 30.2574C-0.565177 34.4258 0.544003 43.2303 13.0971 43.6022C21.3582 43.8453 22.9496 36.4179 23.0757 31.3068C23.5728 31.4099 24.0699 31.4725 24.5966 31.4725C25.3237 31.4725 26.0322 31.3621 26.6999 31.1559C27.2712 31.7009 27.542 32.4226 27.6124 33.2953C27.8536 35.814 23.8436 37.5411 23.7286 41.2124C23.6024 45.042 25.4943 48.0063 30.1127 48.8238C34.3193 49.564 43.1925 48.4519 43.5597 35.9945C43.8008 27.9412 36.5933 26.2694 31.4481 26.0963C31.578 25.5734 31.6485 25.0284 31.6485 24.465C31.6485 23.5886 31.4927 22.7564 31.1922 21.9831C31.7189 21.5265 32.3866 21.3019 33.1842 21.2282C35.7141 20.9999 37.4464 24.9732 41.1597 25.091C45.0102 25.2125 47.9964 23.3419 48.8199 18.7463C49.573 14.5742 48.4489 5.76969 35.8996 5.39778ZM24.4965 26.3541C23.4763 26.3541 22.6343 25.5255 22.6343 24.4982C22.6343 23.4782 23.4763 22.6423 24.4965 22.6423C25.5351 22.6423 26.3735 23.4782 26.3735 24.4982C26.3735 25.5255 25.5351 26.3541 24.4965 26.3541Z" fill="#F5F5F5"/> +</svg>
A backend_iot/static/style.css

@@ -0,0 +1,450 @@

+.surface-pro { + background-color: #000000; + display: flex; + flex-direction: row; + justify-content: center; + width: 100%; +} + +.surface-pro .div { + background-color: #000000; + width: 1440px; + height: 960px; + position: relative; +} + +.surface-pro .overlap { + position: absolute; + width: 288px; + height: 105px; + top: 0; + left: 288px; + background-color: #000000; +} + +.surface-pro .text { + top: 27px; + left: 77px; + color: #ffffff; + font-size: 40px; + white-space: nowrap; + position: absolute; + font-family: "Inter", Helvetica; + font-weight: 400; + text-align: center; + letter-spacing: 0; + line-height: normal; +} + +.surface-pro .text-wrapper { + font-family: "Inter", Helvetica; + font-weight: 400; + color: #ffffff; + font-size: 40px; + letter-spacing: 0; +} + +.surface-pro .span { + font-size: 20px; +} + +.surface-pro .overlap-group { + position: absolute; + width: 288px; + height: 105px; + top: 0; + left: 864px; + background-color: #000000; +} + +.surface-pro .AQI-int { + left: 94px; + position: absolute; + top: 27px; + font-family: "Inter", Helvetica; + font-weight: 400; + color: #ffffff; + font-size: 40px; + text-align: center; + letter-spacing: 0; + line-height: normal; + white-space: nowrap; +} + +.surface-pro .rectangle { + position: absolute; + width: 288px; + height: 12px; + top: 93px; + left: 0; + background-color: #b51010; +} + +.surface-pro .overlap-2 { + position: absolute; + width: 288px; + height: 105px; + top: 0; + left: 1152px; + background-color: #000000; +} + +.surface-pro .AQI-ext { + left: 87px; + position: absolute; + top: 27px; + font-family: "Inter", Helvetica; + font-weight: 400; + color: #ffffff; + font-size: 40px; + text-align: center; + letter-spacing: 0; + line-height: normal; + white-space: nowrap; +} + +.surface-pro .rectangle-2 { + position: absolute; + width: 288px; + height: 12px; + top: 93px; + left: 0; + background-color: #fab639; +} + +.surface-pro .group { + position: absolute; + width: 280px; + height: 532px; + top: 190px; + left: 576px; + background-image: url(./img/volvo-concept-26-interior-06-768x405-1.png); + background-size: cover; + background-position: 50% 50%; +} + +.surface-pro .overlap-3 { + position: absolute; + width: 172px; + height: 239px; + top: 332px; + left: 297px; +} + +.surface-pro .vector { + position: absolute; + width: 49px; + height: 49px; + top: 100px; + left: 123px; +} + +.surface-pro .overlap-wrapper { + position: absolute; + width: 138px; + height: 239px; + top: 0; + left: 0; +} + +.surface-pro .overlap-group-2 { + position: relative; + height: 239px; +} + +.surface-pro .frame-wrapper { + position: absolute; + width: 112px; + height: 221px; + top: 10px; + left: 18px; +} + +.surface-pro .frame { + position: relative; + height: 221px; + overflow: hidden; + overflow-y: scroll; +} + +.surface-pro .element { + top: -310px; + left: 34px; + color: transparent; + font-size: 64px; + position: absolute; + font-family: "Inter", Helvetica; + font-weight: 400; + text-align: center; + letter-spacing: 0; + line-height: normal; +} + +.surface-pro .text-wrapper-2 { + color: #000000; +} + +.surface-pro .text-wrapper-3 { + color: #ffffff; +} + +.surface-pro .rectangle-3 { + position: absolute; + width: 138px; + height: 79px; + top: 0; + left: 0; + background: linear-gradient(180deg, rgb(0, 0, 0) 31.25%, rgba(0, 0, 0, 0) 100%); +} + +.surface-pro .rectangle-4 { + position: absolute; + width: 138px; + height: 79px; + top: 160px; + left: 0; + transform: rotate(-180deg); + background: linear-gradient(180deg, rgb(0, 0, 0) 31.25%, rgba(0, 0, 0, 0) 100%); +} + +.surface-pro .rectangle-5 { + position: absolute; + width: 138px; + height: 79px; + top: 0; + left: 9px; + background: linear-gradient(180deg, rgb(0, 0, 0) 31.25%, rgba(0, 0, 0, 0) 100%); +} + +.surface-pro .rectangle-6 { + position: absolute; + width: 138px; + height: 79px; + top: 160px; + left: 9px; + transform: rotate(-180deg); + background: linear-gradient(180deg, rgb(0, 0, 0) 31.25%, rgba(0, 0, 0, 0) 100%); +} + +.surface-pro .overlap-group-wrapper { + position: absolute; + width: 138px; + height: 239px; + top: 332px; + left: 110px; +} + +.surface-pro .div-wrapper { + position: absolute; + width: 112px; + height: 221px; + top: 10px; + left: 13px; +} + +.surface-pro .p { + width: 109px; + top: -88px; + left: 1px; + color: transparent; + font-size: 64px; + position: absolute; + font-family: "Inter", Helvetica; + font-weight: 400; + text-align: center; + letter-spacing: 0; + line-height: normal; + color: white; +} + +.surface-pro .tint { + position: absolute; + top: 27px; + left: 84px; + font-family: "Inter", Helvetica; + font-weight: 400; + color: #ffffff; + font-size: 40px; + text-align: center; + letter-spacing: 0; + line-height: normal; + white-space: nowrap; +} + +.surface-pro .overlap-4 { + position: absolute; + width: 885px; + height: 152px; + top: 808px; + left: 0; +} + +.surface-pro .group-2 { + position: absolute; + width: 290px; + height: 105px; + top: 47px; + left: 288px; +} + +.surface-pro .overlap-5 { + position: relative; + width: 288px; + height: 105px; + background-color: #000000; +} + +.surface-pro .text-wrapper-4 { + position: absolute; + width: 288px; + top: 29px; + left: 0; + font-family: "Inter", Helvetica; + font-weight: 400; + color: #ffffff; + font-size: 40px; + text-align: center; + letter-spacing: 0; + line-height: normal; +} + +.surface-pro .group-3 { + position: absolute; + width: 290px; + height: 105px; + top: 47px; + left: 0; +} + +.surface-pro .group-4 { + position: absolute; + width: 326px; + height: 127px; + top: 0; + left: 559px; +} + +.surface-pro .overlap-6 { + position: relative; + width: 322px; + height: 127px; + background-color: #000000; + border-radius: 35px; + border: 1px solid; + border-color: #ffffff; +} + +.surface-pro .overlap-group-3 { + position: absolute; + width: 161px; + height: 127px; + top: 0; + left: 0; + background-image: url(./img/tasto-ricircolo.svg); + background-size: 100% 100%; +} + +.surface-pro .text-wrapper-5 { + position: absolute; + width: 161px; + top: 37px; + left: 0; + font-family: "Inter", Helvetica; + font-weight: 400; + color: #ffffff; + font-size: 45px; + text-align: center; + letter-spacing: 0; + line-height: normal; +} + +.surface-pro .text-wrapper-6 { + position: absolute; + width: 161px; + top: 36px; + left: 160px; + font-family: "Inter", Helvetica; + font-weight: 400; + color: #ffffff; + font-size: 45px; + text-align: center; + letter-spacing: 0; + line-height: normal; +} + +.surface-pro .group-5 { + position: absolute; + width: 387px; + height: 357px; + top: 273px; + left: 944px; +} + +.surface-pro .overlap-7 { + position: relative; + width: 385px; + height: 357px; + background-color: #000000; +} + +.surface-pro .text-wrapper-7 { + position: absolute; + top: 111px; + left: 153px; + font-family: "Inter", Helvetica; + font-weight: 400; + color: #ffffff; + font-size: 40px; + text-align: center; + letter-spacing: 0; + line-height: normal; + white-space: nowrap; +} + +.surface-pro .rectangle-7 { + position: absolute; + width: 154px; + height: 61px; + top: 183px; + left: 109px; + background-color: #ffffff; + border-radius: 22px; +} + +.surface-pro .img { + position: absolute; + width: 117px; + height: 52px; + top: 188px; + left: 124px; +} + +.surface-pro .ellipse { + position: absolute; + width: 315px; + height: 315px; + top: 21px; + left: 29px; + border-radius: 157.5px; + border: 30px solid; + border-color: #ff1616; + filter: blur(30px); +} + +.surface-pro .ellipse-2 { + position: absolute; + width: 291px; + height: 291px; + top: 33px; + left: 41px; + border-radius: 145.5px; + border: 6px solid; + border-color: #ffffff; + backdrop-filter: blur(50px) brightness(100%); + -webkit-backdrop-filter: blur(50px) brightness(100%); +} + + +.frame::-webkit-scrollbar { + display: none; +}
M backend_iot/templates/index.htmlbackend_iot/templates/index.html

@@ -1,1 +1,219 @@

-<h1>Hello world!</h1> +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" href="/static/globals.css" /> + <link rel="stylesheet" href="/static/style.css" /> + </head> + <body> + <div class="surface-pro"> + <div class="div"> + <div class="overlap"> + <p class="text"> + <span class="text-wrapper">T</span> + <span class="span">ext</span> + <span class="text-wrapper" id="t_out"> 25°</span> + </p> + </div> + <div class="overlap-group"> + <p class="AQI-int"><span class="text-wrapper">AQI int</span> <span class="span" id="aqi_in">0</span></p> + <div class="rectangle"></div> + </div> + <div class="overlap-2"> + <p class="AQI-ext"><span class="text-wrapper">AQI </span> <span class="span" id="aqi_ext">0</span></p> + <div class="rectangle-2"></div> + </div> + <div class="group"></div> + <div class="overlap-3"> + <img class="vector" src="/static/img/vector.svg" /> + <div class="overlap-wrapper"> + <div class="overlap-group-2"> + <div class="frame-wrapper"> + <div class="frame"> + <p class="p"> + <span class="text-wrapper-2">.<br />.<br /></span> + <span id="speed-4" class="text-wrapper-3" onclick="setFanSpeed(this)">4<br /></span> + <span id="speed-3" class="text-wrapper-3" onclick="setFanSpeed(this)">3<br /></span> + <span id="speed-2" class="text-wrapper-3" onclick="setFanSpeed(this)">2<br /></span> + <span id="speed-1" class="text-wrapper-3" onclick="setFanSpeed(this)">1<br /></span> + <span class="text-wrapper-2">.<br /></span> + </p> + </div> + </div> + <div class="rectangle-3"></div> + <div class="rectangle-4"></div> + </div> + </div> + <div class="rectangle-5"></div> + <div class="rectangle-6"></div> + </div> + <div class="overlap-group-wrapper"> + <div class="overlap-group-2"> + <div class="div-wrapper"> + <div class="frame"> + <p class="p"> + <span id="temp-28" class="text-wrapper-3">.<br /></span> + <span id="temp-28" class="text-wrapper-3">.<br /></span> + <span id="temp-28" class="text-wrapper-3" onclick="setTarget(this)">28°<br /></span> + <span id="temp-27" class="text-wrapper-3" onclick="setTarget(this)">27°<br /></span> + <span id="temp-26" class="text-wrapper-3" onclick="setTarget(this)">26°<br /></span> + <span id="temp-25" class="text-wrapper-3" onclick="setTarget(this)">25°<br /></span> + <span id="temp-24" class="text-wrapper-3" onclick="setTarget(this)">24°<br /></span> + <span id="temp-23" class="text-wrapper-3" onclick="setTarget(this)">23°<br /></span> + <span id="temp-22" class="text-wrapper-3" onclick="setTarget(this)">22°<br /></span> + <span id="temp-21" class="text-wrapper-3" onclick="setTarget(this)">21°<br /></span> + <span id="temp-20" class="text-wrapper-3" onclick="setTarget(this)">20°<br /></span> + <span id="temp-19" class="text-wrapper-3" onclick="setTarget(this)">19°<br /></span> + <span id="temp-18" class="text-wrapper-3" onclick="setTarget(this)">18°<br /></span> + <span id="temp-28" class="text-wrapper-3">.<br /></span> + <span id="temp-28" class="text-wrapper-3">.<br /></span> + </p> + </div> + </div> + <div class="rectangle-3"></div> + <div class="rectangle-4"></div> + </div> + </div> + <p class="tint"> + <span class="text-wrapper">T</span> + <span class="span">int</span> + <span class="text-wrapper" id="t_in">22°</span> + </p> + <div class="overlap-4"> + <div class="group-2"> + <div class="overlap-5"><div class="text-wrapper-4">AUTO</div></div> + </div> + <div class="group-3"> + <div class="overlap-5"><div class="text-wrapper-4">MANUAL</div></div> + </div> + <div class="group-4"> + <div class="overlap-6"> + <div class="overlap-group-3"><div class="text-wrapper-5" onclick="setPower(this)">ON</div></div> + <div class="text-wrapper-6" onclick="setPower(this)">OFF</div> + </div> + </div> + <div class="group-6"> + <div class="overlap-5"><div class="text-wrapper-4"></div></div> + </div> + <div class="group-7"> + <div class="overlap-5"><div class="text-wrapper-4"></div></div> + </div> + </div> + <div class="group-5"> + <div class="overlap-7"> + <div class="text-wrapper-7">AQI</div> + <div class="rectangle-7"></div> + <img class="img" src="/static/img/vector-1.svg" /> + <div class="ellipse"></div> + <div class="ellipse-2"></div> + </div> + </div> + </div> + </div> + <script> + function setTarget(element) { + var temperature = element.innerText.trim().replace('°',''); + console.log('Temperatura selezionata:', temperature); + + tosend= {'t_in': temperature} + console.log("Post: ",tosend); + postData(tosend); + } + + function setPower(element) { + var power = element.innerText.trim(); + if (power =="ON") + power = 1; + else power = 0; + tosend = {'power': power} + console.log('Accensione: :', power); + postData(tosend) + } + + function setFanSpeed(element) { + var speed = element.innerText.trim(); + switch (speed){ + case "1": + speed = 500; + break; + case "2": + speed = 1000; + break; + case "3": + speed = 2000; + break; + case "4": + speed = 3000; + break; + } + tosend = {'fan_speed': speed} + console.log('Fan_speed: :', tosend); + postData(tosend) + } + + function setAuto(element) { + var attivazione = element.innerText.trim(); + if(attivazione == "MANUAL") + attivazione = 0; + else + attivazione = 1; + tosend = {'auto': attivazione} + console.log('Auto/Man selezionato:', attivazione); + postData(tosend) + } + + + function postData(data) { + let formBody = []; + for (var property in data) { + var encodedKey = encodeURIComponent(property); + var encodedValue = encodeURIComponent(data[property]); + formBody.push(encodedKey + "=" + encodedValue); + } + formBody = formBody.join("&"); + + fetch('http://localhost:1111/data', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' + }, + body: formBody + }) + } + + + async function getData() { + const response = await fetch('http://localhost:1111/data') + console.log(response); + const data= await response.json(); + console.log(data); + + switch(data) { + case data.t_in: + document.getElementById("t_in").innerHTML = data[1]; + break; + case "t_out": + document.getElementById("t_out").innerHTML = data[1]; + break; + case "aqi": + document.getElementById("aqi_ext").innerHTML = data[1]; + break; + case "aqi_in": + document.getElementById("aqi_in").innerHTML = data[1]; + break; + case "air_flow": + break; + case "fan_speed": + document.getElementById("aqi_in").innerHTML = data[1]; + break; + } + window.addEventListener('load', function () { + // Your document is loaded. + var fetchInterval = 5000; // 5 seconds. + + // Invoke the request every 5 seconds. + setInterval(getData, fetchInterval); + }); + } + </script> + </body> +</html>
M backend_iot/views.pybackend_iot/views.py

@@ -1,26 +1,9 @@

from backend_iot import app -from backend_iot.db import getLatestRecord, addRecord, get_aqi -from flask import request, redirect, render_template, abort -from playhouse.shortcuts import model_to_dict -from datetime import datetime - -current_latitude, current_longitude = 45.46437891252755, 7.872049153560152 - -def update_record(record): - record.update() - record.latitude = current_latitude - record.longitude = current_longitude - record.timestamp = datetime.now() - record.aqi = get_aqi(current_latitude, current_longitude) - return record - -def record_to_dict(record): - temp = model_to_dict(record) - del temp["id"] - return temp +from backend_iot.db import get_latest_record, handle_new_data, record_to_dict +from flask import request, render_template, abort def latest_or_abort(): - latest = getLatestRecord() + latest = get_latest_record() if latest is None: return abort(400) return record_to_dict(latest)

@@ -30,25 +13,8 @@ def index_route():

if request.method == 'GET': return render_template("index.html") -editable_fields = ["t_target", "power", "fan_speed", "auto", "airflow"] - @app.route("/data", methods = ['GET', 'POST']) def data_route(): - if request.method == "GET": return latest_or_abort() - - latest = getLatestRecord() - form = request.form - - for key in form.keys(): - if key not in latest.keys(): - continue - if key == "fan_speed" and latest["auto"]: - continue - latest[key] = form[key] - - new_record = update_record(latest) - temp = record_to_dict(new_record) - addRecord(**temp) - return temp + return handle_new_data(request.form)