Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc413a39d6 | ||
|
|
34264a1913 | ||
|
|
7f1c12d758 | ||
|
|
2905d1f894 |
30
README.md
30
README.md
@ -5,3 +5,33 @@ A school project to control a simulated ventilation system based on measured CO2
|
||||
## Embedded Systems Programming Ventilation Project
|
||||
|
||||
The objective of this project was to create a smart ventilation system that has the capability to display sensor data and let users set the ventilation fan speed manually, or let the system determine the fan speed in automatic operation by setting the target output air pressure to achieve. This documentation examines the overall system architecture in hardware and software, operation of the system, the development process and goes in detail of the technical implementation. As hardware implementation of this ventilation system is pre-determined, particular attention in this documentation is paid to the embedded software and communications implementation.
|
||||
|
||||
## Download latest release
|
||||
### [esp-vent-main v1.0.0](https://github.com/vas-dav/ESP-Ventilation/releases) (2022-10-31)
|
||||
> Source code is safely building on **MCUXpresso IDE v11.5.0 [Build 7232] [2022-01-11]**
|
||||
### Current functionality:
|
||||
### Modes:
|
||||
##### Manual:
|
||||
```
|
||||
- Buttons to inc/dec fan speed(0-10V) (show in percents 10% == 1V)
|
||||
- Display fan speed + pressure.
|
||||
- Fan updates at the same time as UI
|
||||
- BTN1 inc voltage, BTN2 dec voltage
|
||||
```
|
||||
##### Automatic:
|
||||
```
|
||||
- Buttons to inc/dec fan pressure level (0-120 Pa) (show in percents 10% == 1V)
|
||||
- Pressure to inc/dec fan speed
|
||||
- BTN1 inc pressure, BTN2 dec pressure
|
||||
```
|
||||
##### Additional:
|
||||
```
|
||||
- BTN3 switch between modes
|
||||
- BTN4 show sensor values
|
||||
```
|
||||
|
||||
## Latest Changes
|
||||
* [Closed Pull-requests](https://github.com/vas-dav/ESP-Ventilation/pulls?q=is%3Apr+is%3Aclosed)
|
||||
|
||||
#### [Open issues](https://github.com/vas-dav/ESP-Ventilation/issues)
|
||||
|
||||
|
||||
31158
WebUI/data.json
31158
WebUI/data.json
File diff suppressed because it is too large
Load Diff
@ -10,8 +10,11 @@
|
||||
<script src="main.js" defer></script>
|
||||
<script src="https://cdn.socket.io/socket.io-3.0.1.min.js"></script>
|
||||
</head>
|
||||
<body onload="checkUser(), checkMode(), getStartValues(), updateChart()">
|
||||
<body onload="checkUser(), getStartValues(), updateChart()">
|
||||
<header>
|
||||
<form id="pre-reg" method="get" action="/register" style="margin-top: 10px; margin-right: 10px;">
|
||||
<input id="reg" type="submit" value="Register new user">
|
||||
</form>
|
||||
<h1>ABB Ventilation Controller</h1>
|
||||
</header>
|
||||
<div class="logout">
|
||||
@ -132,6 +135,7 @@
|
||||
</div>
|
||||
<div id="user-table">
|
||||
<h2>User Log History</h2>
|
||||
<div id="log">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@ -145,5 +149,6 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
114
WebUI/index.js
114
WebUI/index.js
@ -60,6 +60,13 @@ function getTime(){
|
||||
return today;
|
||||
}
|
||||
|
||||
const isLogged = (req, res, next) =>{
|
||||
if(!req.session.userId){
|
||||
res.send(`<h1 style="text-align:center; margin-top:50px;">This page is only for registered users.</h1>`);
|
||||
}
|
||||
else next()
|
||||
}
|
||||
|
||||
client.on('connect', ()=>{
|
||||
console.log('MQTT client connected: '+ client.connected);
|
||||
});
|
||||
@ -94,22 +101,10 @@ client.on('message', async (topic, message) =>{
|
||||
io.emit('data', newData);
|
||||
});
|
||||
|
||||
|
||||
app.get('/', (req, res)=>{
|
||||
res.sendFile(path.join(__dirname + '/index.html'));
|
||||
})
|
||||
|
||||
app.get('/register', (req,res)=>{
|
||||
res.send(`
|
||||
<h1>Register</h1>
|
||||
<form method='post' action='/register' />
|
||||
<input type='text' name='name' placeholder='Username' required />
|
||||
<input type='password' name='password' placeholder='Password' required />
|
||||
<input type='submit' />
|
||||
</form>
|
||||
`);
|
||||
});
|
||||
|
||||
app.post('/', async (req,res)=>{
|
||||
let pwd;
|
||||
const { username, password } = req.body;
|
||||
@ -129,7 +124,6 @@ app.post('/', async (req,res)=>{
|
||||
let y = now.slice(0,4);
|
||||
let t = now.slice(11, 19);
|
||||
let stamp = d + '.' + m + '.' + y + ' ' + t;
|
||||
//console.log(stamp);
|
||||
req.session.userId = username;
|
||||
req.session.startTime = stamp;
|
||||
console.log('Password is correct');
|
||||
@ -145,7 +139,52 @@ app.post('/', async (req,res)=>{
|
||||
console.log('Wrong password');
|
||||
res.status(205);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
app.post('/logout', async (req, res) =>{
|
||||
let now = getTime();
|
||||
let d = now.slice(8,10);
|
||||
let m = now.slice(5,7);
|
||||
let y = now.slice(0,4);
|
||||
let t = now.slice(11, 19);
|
||||
let stamp = d + '.' + m + '.' + y + ' ' + t;
|
||||
req.session.endTime = stamp;
|
||||
let sesEnd = req.session.endTime;
|
||||
let log = [];
|
||||
try {
|
||||
log = await read('user_log.json');
|
||||
} catch (e) { console.log(e); }
|
||||
|
||||
let newLog = {
|
||||
"id": log.length +1,
|
||||
"UserId": sesUser,
|
||||
"Login": sesStart,
|
||||
"Logout": sesEnd
|
||||
}
|
||||
log.unshift(newLog);
|
||||
write(log, 'user_log.json');
|
||||
|
||||
req.session.destroy(err => {
|
||||
if(err){
|
||||
return res.redirect('/');
|
||||
}
|
||||
res.clearCookie('sid');
|
||||
res.redirect('/');
|
||||
});
|
||||
});
|
||||
|
||||
app.use(isLogged);
|
||||
|
||||
app.get('/register', (req,res)=>{
|
||||
res.send(`
|
||||
<h1 style="text-align:center; margin-top:50px;">Register new user</h1>
|
||||
<form method='post' action='/register' style="text-align: center;"/>
|
||||
<input type='text' name='name' placeholder='Username' required /><br>
|
||||
<input type='password' name='password' placeholder='Password' required /><br>
|
||||
<input type='submit' id='post-reg' value="Send"/>
|
||||
</form>
|
||||
`);
|
||||
});
|
||||
|
||||
app.post('/register', async (req,res) =>{
|
||||
let users = [];
|
||||
@ -176,50 +215,7 @@ app.post('/register', async (req,res) =>{
|
||||
console.log('User exists already. No registeration done.');
|
||||
}
|
||||
}
|
||||
res.redirect('/register')
|
||||
})
|
||||
|
||||
app.post('/logout',async (req, res) =>{
|
||||
let now = getTime();
|
||||
let d = now.slice(8,10);
|
||||
let m = now.slice(5,7);
|
||||
let y = now.slice(0,4);
|
||||
let t = now.slice(11, 19);
|
||||
let stamp = d + '.' + m + '.' + y + ' ' + t;
|
||||
req.session.endTime = stamp;
|
||||
let sesEnd = req.session.endTime;
|
||||
let log = [];
|
||||
try {
|
||||
log = await read('user_log.json');
|
||||
} catch (e) { console.log(e); }
|
||||
|
||||
let newLog = {
|
||||
"id": log.length +1,
|
||||
"UserId": sesUser,
|
||||
"Login": sesStart,
|
||||
"Logout": sesEnd
|
||||
}
|
||||
console.log(newLog);
|
||||
log.unshift(newLog);
|
||||
write(log, 'user_log.json');
|
||||
|
||||
req.session.destroy(err => {
|
||||
if(err){
|
||||
return res.redirect('/');
|
||||
}
|
||||
res.clearCookie('sid');
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
res.redirect('/')
|
||||
});
|
||||
/*
|
||||
app.get('/data', async (req, res) => {
|
||||
try {
|
||||
const data = await read('data.json');
|
||||
res.json(data);
|
||||
} catch (e) {
|
||||
res.status(404).send(e);
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
server.listen(3000, () => console.log('Server listening on port 3000'));
|
||||
@ -18,8 +18,8 @@ const end = document.getElementById('end-time');
|
||||
const reset = document.getElementById('btn_reset');
|
||||
|
||||
let user;
|
||||
let pointerX = -1;
|
||||
let pointerY = -1;
|
||||
let pointX = -1;
|
||||
let pointY = -1;
|
||||
let lastX = 0;
|
||||
let lastY = 0;
|
||||
let counter = 0;
|
||||
@ -56,13 +56,12 @@ socket.on('pwd', (data) =>{
|
||||
if(data){
|
||||
sessionStorage.setItem('reload', true);
|
||||
document.location.reload();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('user', (data) =>{
|
||||
user = data;
|
||||
localStorage.setItem('user', data);
|
||||
sessionStorage.setItem('user', data);
|
||||
sessionStorage.setItem('loggedIn', 'true');
|
||||
});
|
||||
|
||||
@ -72,9 +71,6 @@ reset.addEventListener('click', e =>{
|
||||
end.value = today.replace('T', ' ') + ':00';
|
||||
start_time = new Date(start.value).getTime();
|
||||
end_time = new Date(end.value).getTime();
|
||||
|
||||
//start.value = today.replace('T', ' ') + ':00';
|
||||
//end.value = today.replace('T', ' ') + ':00';
|
||||
updateChart();
|
||||
})
|
||||
|
||||
@ -122,12 +118,8 @@ automode.addEventListener('click', () =>{
|
||||
manmode.addEventListener('click', () =>{
|
||||
if(!sessionStorage.getItem('loggedIn')){
|
||||
document.getElementById('login-form').style.display = "block";
|
||||
document.getElementById('sp_div').style.display = "none";
|
||||
document.getElementById('pr_div').style.display = "none";
|
||||
document.getElementById('m_auto').style.display = "none";
|
||||
document.getElementById('m_man').style.display = "none";
|
||||
document.getElementById('auto_label').style.display = "none";
|
||||
document.getElementById('man_label').style.display = "none";
|
||||
document.getElementsByClassName('modes').style.display = "none";
|
||||
document.getElementsByClassName('set_values').style.display = "none";
|
||||
}
|
||||
else{
|
||||
document.getElementById('login-form').style.display = "none";
|
||||
@ -154,17 +146,20 @@ document.getElementById('password').addEventListener('click', ()=>{
|
||||
});
|
||||
|
||||
log_out.addEventListener('click', () =>{
|
||||
console.log('log out clicked');
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
})
|
||||
|
||||
document.getElementById('reg').addEventListener('click', ()=>{
|
||||
sessionStorage.setItem('reload', true);
|
||||
})
|
||||
|
||||
window.addEventListener('beforeunload', (e)=>{
|
||||
if(document.cookie){
|
||||
log_out.click();
|
||||
if(!sessionStorage.getItem('reload')){
|
||||
logOutUser();
|
||||
}
|
||||
});
|
||||
|
||||
// Changes the border-color of output elements in monitor fieldset if values are outside the ranges
|
||||
function circleColor(){
|
||||
if(g_pressure.value > 115){
|
||||
g_pressure.style.borderColor = "red";
|
||||
@ -198,85 +193,64 @@ function circleColor(){
|
||||
}
|
||||
}
|
||||
|
||||
function logOutUser(){
|
||||
if(document.cookie){
|
||||
log_out.click();
|
||||
}
|
||||
}
|
||||
|
||||
document.onmousemove = function(event) {
|
||||
pointerX = event.pageX;
|
||||
pointerY = event.pageY;
|
||||
pointX = event.pageX;
|
||||
pointY = event.pageY;
|
||||
}
|
||||
setInterval(activityCheck, 1000);
|
||||
|
||||
// Keeps the counter if no mouse moves are detected. If the set counter limit is exceeded, the user is logged out
|
||||
function activityCheck() {
|
||||
if(sessionStorage.getItem('loggedIn')){
|
||||
if(pointerX - lastX === 0 && pointerY - lastY === 0){
|
||||
if(pointX - lastX === 0 && pointY - lastY === 0){
|
||||
counter = counter + 1;
|
||||
}
|
||||
else{
|
||||
lastX = pointerX;
|
||||
lastY = pointerY;
|
||||
lastX = pointX;
|
||||
lastY = pointY;
|
||||
counter = 0;
|
||||
}
|
||||
}
|
||||
if(counter > 60){
|
||||
if(counter > 600){
|
||||
logOutUser();
|
||||
}
|
||||
}
|
||||
|
||||
// Logs out the user, click of the log out button cleares session storage with the event listener of the button
|
||||
function logOutUser(){
|
||||
if(sessionStorage.getItem('loggedIn')){
|
||||
log_out.click();
|
||||
}
|
||||
}
|
||||
|
||||
// Session storage is used to check which elements are visible when the body of the page is loaded
|
||||
function checkUser(){
|
||||
if(sessionStorage.getItem('reload')){
|
||||
document.getElementById('login-form').style.display = "block";
|
||||
document.getElementById('pwd-warn').style.display = "block";
|
||||
sessionStorage.removeItem('reload');
|
||||
}
|
||||
if(document.cookie && sessionStorage.getItem('loggedIn')){
|
||||
if(sessionStorage.getItem('loggedIn')){
|
||||
document.getElementById('login-form').style.display = "none";
|
||||
document.getElementById('user').style.display = "block";
|
||||
document.getElementById('user').innerHTML = 'Signed in user: ' + localStorage.getItem('user');
|
||||
document.getElementById('user').innerHTML = 'Signed in user: ' + sessionStorage.getItem('user');
|
||||
document.getElementById('btn_log_out').style.display = "block";
|
||||
document.getElementById('user').style.display = "block";
|
||||
document.getElementById('user').innerHTML = 'Signed in user: ' + localStorage.getItem('user');
|
||||
document.getElementById('btn_log_out').style.display = "block";
|
||||
document.getElementById('user-table').style.width = "45%";
|
||||
document.getElementById('chart-cont').style.width = "50%";
|
||||
|
||||
if(manmode.checked = true){
|
||||
s_pressure.disabled = true;
|
||||
document.getElementById('pr_div').style.opacity = 0.4;
|
||||
document.getElementById('sp_div').style.opacity = 1;
|
||||
s_speed.disabled = false;
|
||||
}
|
||||
if(automode.checked = true){
|
||||
s_speed.disabled = true;
|
||||
document.getElementById('sp_div').style.opacity = 0.4;
|
||||
s_pressure.disabled = false;
|
||||
}
|
||||
}
|
||||
else{
|
||||
document.getElementById('login-form').style.display = "block";
|
||||
document.getElementById('sp_div').style.display = "none";
|
||||
document.getElementById('pr_div').style.display = "none";
|
||||
document.getElementById('m_auto').style.display = "none";
|
||||
document.getElementById('m_man').style.display = "none";
|
||||
document.getElementById('auto_label').style.display = "none";
|
||||
document.getElementById('man_label').style.display = "none";
|
||||
document.getElementById('user-table').style.display = "none";
|
||||
document.getElementById('chart-cont').style.width = "95%";
|
||||
}
|
||||
}
|
||||
|
||||
function checkMode(){
|
||||
if(document.cookie && sessionStorage.getItem('loggedIn')){
|
||||
automode.checked = true;
|
||||
s_speed.disabled = true;
|
||||
document.getElementById('sp_div').style.opacity = 0.4;
|
||||
document.getElementById('pr_div').style.opacity = 1;
|
||||
s_pressure.disabled = false;
|
||||
}
|
||||
}
|
||||
else{
|
||||
document.getElementById('login-form').style.display = "block";
|
||||
document.getElementById('pre-reg').style.display = "none";
|
||||
document.getElementById('sp_div').style.display = "none";
|
||||
document.getElementById('pr_div').style.display = "none";
|
||||
document.getElementById('m_auto').style.display = "none";
|
||||
@ -287,16 +261,19 @@ function checkMode(){
|
||||
}
|
||||
}
|
||||
|
||||
// Sends the selected pressure value with socket to the server
|
||||
function sendPressure(){
|
||||
let press = { auto: true, pressure: parseInt(s_pressure.value) }
|
||||
socket.emit('setting', press);
|
||||
}
|
||||
|
||||
// Sends the selected fan speed value with socket to the server
|
||||
function sendSpeed(){
|
||||
let speed = { auto: false, speed: parseInt(s_speed.value) }
|
||||
socket.emit('setting', speed);
|
||||
}
|
||||
|
||||
// Updates the data chart on the page, fetches values from json file (db) and shows the data with selected time interval
|
||||
function updateChart(){
|
||||
async function fetchData(){
|
||||
const response = await fetch('data.json');
|
||||
@ -345,6 +322,7 @@ function updateChart(){
|
||||
});
|
||||
};
|
||||
|
||||
// data and config are base settings for the data chart
|
||||
const data = {
|
||||
datasets: [{
|
||||
label: 'CO2',
|
||||
@ -433,6 +411,7 @@ const config = {
|
||||
|
||||
const myChart = new Chart(canvas, config);
|
||||
|
||||
// Fetches the data from json file (db) and updates the the user log history table
|
||||
fetch('user_log.json')
|
||||
.then((res)=>{
|
||||
return res.json();
|
||||
@ -452,6 +431,7 @@ fetch('user_log.json')
|
||||
placeholder.innerHTML = out;
|
||||
});
|
||||
|
||||
// Fetches the data from json file (db) and sets the latest values to the monitor fieldset output elements when the body of the page is loaded
|
||||
async function getStartValues(){
|
||||
await fetch('data.json')
|
||||
.then((res) =>{
|
||||
|
||||
@ -71,6 +71,19 @@ h4{
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
#pre-reg{
|
||||
display: block;
|
||||
position: relative;
|
||||
float:right;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#reg{
|
||||
border-radius: 5px;
|
||||
color: black;
|
||||
background-color: #CCE3DE;
|
||||
}
|
||||
|
||||
.logout{
|
||||
margin-left: 20px;
|
||||
margin-top: 35px;
|
||||
@ -91,6 +104,7 @@ h4{
|
||||
border-radius: 5px;
|
||||
background-color: #CCE3DE;
|
||||
border: 0.5px solid;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#user{
|
||||
@ -289,11 +303,19 @@ canvas{
|
||||
margin-bottom: 50px;
|
||||
width: 50%;
|
||||
margin: auto;
|
||||
|
||||
}
|
||||
|
||||
#log{
|
||||
height: 300px;
|
||||
overflow: hidden;
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table{
|
||||
background-color: #CCE3DE;
|
||||
width: 90%;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
@ -302,6 +324,12 @@ table, th, td {
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.th.fixed{
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
td{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -1,4 +1,34 @@
|
||||
[
|
||||
{
|
||||
"id": 20,
|
||||
"UserId": "Miisa",
|
||||
"Login": "28.10.2022 11:48:28",
|
||||
"Logout": "28.10.2022 11:55:17"
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"UserId": "Miisa",
|
||||
"Login": "28.10.2022 11:38:52",
|
||||
"Logout": "28.10.2022 11:45:02"
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"UserId": "Miisa",
|
||||
"Login": "28.10.2022 08:28:47",
|
||||
"Logout": "28.10.2022 08:29:58"
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"UserId": "Miisa",
|
||||
"Login": "28.10.2022 08:25:47",
|
||||
"Logout": "28.10.2022 08:27:40"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"UserId": "Jaakko",
|
||||
"Login": "28.10.2022 08:15:12",
|
||||
"Logout": "28.10.2022 08:19:01"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"UserId": "Miisa",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user