Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

7 changed files with 643 additions and 30760 deletions

View File

@ -5,33 +5,3 @@ A school project to control a simulated ventilation system based on measured CO2
## Embedded Systems Programming Ventilation Project ## 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. 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)

File diff suppressed because it is too large Load Diff

View File

@ -10,12 +10,9 @@
<script src="main.js" defer></script> <script src="main.js" defer></script>
<script src="https://cdn.socket.io/socket.io-3.0.1.min.js"></script> <script src="https://cdn.socket.io/socket.io-3.0.1.min.js"></script>
</head> </head>
<body onload="checkUser(), getStartValues(), updateChart()"> <body onload="checkUser(), checkMode(), getStartValues(), updateChart()">
<header> <header>
<form id="pre-reg" method="get" action="/register" style="margin-top: 10px; margin-right: 10px;"> <h1>ABB Ventilation Controller</h1>
<input id="reg" type="submit" value="Register new user">
</form>
<h1>ABB Ventilation Controller</h1>
</header> </header>
<div class="logout"> <div class="logout">
<p id="user"></p> <p id="user"></p>
@ -135,19 +132,17 @@
</div> </div>
<div id="user-table"> <div id="user-table">
<h2>User Log History</h2> <h2>User Log History</h2>
<div id="log"> <table>
<table> <thead>
<thead> <tr>
<tr> <th>User</th>
<th>User</th> <th>Login time</th>
<th>Login time</th> <th>Logout time</th>
<th>Logout time</th> </tr>
</tr> </thead>
</thead> <tbody id="table-output">
<tbody id="table-output"> </tbody>
</tbody> </table>
</table>
</div>
</div> </div>
</div> </div>
</body> </body>

View File

@ -60,13 +60,6 @@ function getTime(){
return today; 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', ()=>{ client.on('connect', ()=>{
console.log('MQTT client connected: '+ client.connected); console.log('MQTT client connected: '+ client.connected);
}); });
@ -101,10 +94,22 @@ client.on('message', async (topic, message) =>{
io.emit('data', newData); io.emit('data', newData);
}); });
app.get('/', (req, res)=>{ app.get('/', (req, res)=>{
res.sendFile(path.join(__dirname + '/index.html')); 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)=>{ app.post('/', async (req,res)=>{
let pwd; let pwd;
const { username, password } = req.body; const { username, password } = req.body;
@ -124,6 +129,7 @@ app.post('/', async (req,res)=>{
let y = now.slice(0,4); let y = now.slice(0,4);
let t = now.slice(11, 19); let t = now.slice(11, 19);
let stamp = d + '.' + m + '.' + y + ' ' + t; let stamp = d + '.' + m + '.' + y + ' ' + t;
//console.log(stamp);
req.session.userId = username; req.session.userId = username;
req.session.startTime = stamp; req.session.startTime = stamp;
console.log('Password is correct'); console.log('Password is correct');
@ -139,52 +145,7 @@ app.post('/', async (req,res)=>{
console.log('Wrong password'); console.log('Wrong password');
res.status(205); 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) =>{ app.post('/register', async (req,res) =>{
let users = []; let users = [];
@ -215,7 +176,50 @@ app.post('/register', async (req,res) =>{
console.log('User exists already. No registeration done.'); console.log('User exists already. No registeration done.');
} }
} }
res.redirect('/') 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('/');
});
});
/*
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')); server.listen(3000, () => console.log('Server listening on port 3000'));

View File

@ -18,8 +18,8 @@ const end = document.getElementById('end-time');
const reset = document.getElementById('btn_reset'); const reset = document.getElementById('btn_reset');
let user; let user;
let pointX = -1; let pointerX = -1;
let pointY = -1; let pointerY = -1;
let lastX = 0; let lastX = 0;
let lastY = 0; let lastY = 0;
let counter = 0; let counter = 0;
@ -55,13 +55,14 @@ socket.on('data', (data) =>{
socket.on('pwd', (data) =>{ socket.on('pwd', (data) =>{
if(data){ if(data){
sessionStorage.setItem('reload', true); sessionStorage.setItem('reload', true);
document.location.reload(); document.location.reload();
} }
}); });
socket.on('user', (data) =>{ socket.on('user', (data) =>{
user = data; user = data;
sessionStorage.setItem('user', data); localStorage.setItem('user', data);
sessionStorage.setItem('loggedIn', 'true'); sessionStorage.setItem('loggedIn', 'true');
}); });
@ -71,6 +72,9 @@ reset.addEventListener('click', e =>{
end.value = today.replace('T', ' ') + ':00'; end.value = today.replace('T', ' ') + ':00';
start_time = new Date(start.value).getTime(); start_time = new Date(start.value).getTime();
end_time = new Date(end.value).getTime(); end_time = new Date(end.value).getTime();
//start.value = today.replace('T', ' ') + ':00';
//end.value = today.replace('T', ' ') + ':00';
updateChart(); updateChart();
}) })
@ -110,7 +114,7 @@ automode.addEventListener('click', () =>{
document.getElementById('pr_div').style.opacity = 1; document.getElementById('pr_div').style.opacity = 1;
s_speed.disabled = true; s_speed.disabled = true;
document.getElementById('sp_div').style.opacity = 0.4; document.getElementById('sp_div').style.opacity = 0.4;
} }
}) })
@ -118,8 +122,12 @@ automode.addEventListener('click', () =>{
manmode.addEventListener('click', () =>{ manmode.addEventListener('click', () =>{
if(!sessionStorage.getItem('loggedIn')){ if(!sessionStorage.getItem('loggedIn')){
document.getElementById('login-form').style.display = "block"; document.getElementById('login-form').style.display = "block";
document.getElementsByClassName('modes').style.display = "none"; document.getElementById('sp_div').style.display = "none";
document.getElementsByClassName('set_values').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";
} }
else{ else{
document.getElementById('login-form').style.display = "none"; document.getElementById('login-form').style.display = "none";
@ -146,20 +154,17 @@ document.getElementById('password').addEventListener('click', ()=>{
}); });
log_out.addEventListener('click', () =>{ log_out.addEventListener('click', () =>{
console.log('log out clicked');
localStorage.clear();
sessionStorage.clear(); sessionStorage.clear();
}) })
document.getElementById('reg').addEventListener('click', ()=>{
sessionStorage.setItem('reload', true);
})
window.addEventListener('beforeunload', (e)=>{ window.addEventListener('beforeunload', (e)=>{
if(!sessionStorage.getItem('reload')){ if(document.cookie){
logOutUser(); log_out.click();
} }
}); });
// Changes the border-color of output elements in monitor fieldset if values are outside the ranges
function circleColor(){ function circleColor(){
if(g_pressure.value > 115){ if(g_pressure.value > 115){
g_pressure.style.borderColor = "red"; g_pressure.style.borderColor = "red";
@ -193,87 +198,105 @@ function circleColor(){
} }
} }
document.onmousemove = function(event) {
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(pointX - lastX === 0 && pointY - lastY === 0){
counter = counter + 1;
}
else{
lastX = pointX;
lastY = pointY;
counter = 0;
}
}
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(){ function logOutUser(){
if(sessionStorage.getItem('loggedIn')){ if(document.cookie){
log_out.click(); log_out.click();
} }
} }
// Session storage is used to check which elements are visible when the body of the page is loaded document.onmousemove = function(event) {
pointerX = event.pageX;
pointerY = event.pageY;
}
setInterval(activityCheck, 1000);
function activityCheck() {
if(sessionStorage.getItem('loggedIn')){
if(pointerX - lastX === 0 && pointerY - lastY === 0){
counter = counter + 1;
}
else{
lastX = pointerX;
lastY = pointerY;
counter = 0;
}
}
if(counter > 60){
log_out.click();
}
}
function checkUser(){ function checkUser(){
if(sessionStorage.getItem('reload')){ if(sessionStorage.getItem('reload')){
document.getElementById('login-form').style.display = "block"; document.getElementById('login-form').style.display = "block";
document.getElementById('pwd-warn').style.display = "block"; document.getElementById('pwd-warn').style.display = "block";
sessionStorage.removeItem('reload'); sessionStorage.removeItem('reload');
} }
if(sessionStorage.getItem('loggedIn')){ if(document.cookie && sessionStorage.getItem('loggedIn')){
document.getElementById('login-form').style.display = "none"; document.getElementById('login-form').style.display = "none";
document.getElementById('user').style.display = "block"; document.getElementById('user').style.display = "block";
document.getElementById('user').innerHTML = 'Signed in user: ' + sessionStorage.getItem('user'); document.getElementById('user').innerHTML = 'Signed in user: ' + localStorage.getItem('user');
document.getElementById('btn_log_out').style.display = "block"; 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){ if(manmode.checked = true){
s_pressure.disabled = true; s_pressure.disabled = true;
document.getElementById('pr_div').style.opacity = 0.4; document.getElementById('pr_div').style.opacity = 0.4;
document.getElementById('sp_div').style.opacity = 1;
s_speed.disabled = false; s_speed.disabled = false;
} }
if(automode.checked = true){ if(automode.checked = true){
s_speed.disabled = true; s_speed.disabled = true;
document.getElementById('sp_div').style.opacity = 0.4; document.getElementById('sp_div').style.opacity = 0.4;
document.getElementById('pr_div').style.opacity = 1;
s_pressure.disabled = false; s_pressure.disabled = false;
} }
} }
else{ else{
document.getElementById('login-form').style.display = "block"; document.getElementById('login-form').style.display = "block";
document.getElementById('pre-reg').style.display = "none";
document.getElementById('sp_div').style.display = "none"; document.getElementById('sp_div').style.display = "none";
document.getElementById('pr_div').style.display = "none"; document.getElementById('pr_div').style.display = "none";
document.getElementById('m_auto').style.display = "none"; document.getElementById('m_auto').style.display = "none";
document.getElementById('m_man').style.display = "none"; document.getElementById('m_man').style.display = "none";
document.getElementById('auto_label').style.display = "none"; document.getElementById('auto_label').style.display = "none";
document.getElementById('man_label').style.display = "none"; document.getElementById('man_label').style.display = "none";
document.getElementById('user-table').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('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";
} }
} }
// Sends the selected pressure value with socket to the server
function sendPressure(){ function sendPressure(){
let press = { auto: true, pressure: parseInt(s_pressure.value) } let press = { auto: true, pressure: parseInt(s_pressure.value) }
socket.emit('setting', press); socket.emit('setting', press);
} }
// Sends the selected fan speed value with socket to the server
function sendSpeed(){ function sendSpeed(){
let speed = { auto: false, speed: parseInt(s_speed.value) } let speed = { auto: false, speed: parseInt(s_speed.value) }
socket.emit('setting', speed); 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(){ function updateChart(){
async function fetchData(){ async function fetchData(){
const response = await fetch('data.json'); const response = await fetch('data.json');
@ -322,7 +345,6 @@ function updateChart(){
}); });
}; };
// data and config are base settings for the data chart
const data = { const data = {
datasets: [{ datasets: [{
label: 'CO2', label: 'CO2',
@ -411,7 +433,6 @@ const config = {
const myChart = new Chart(canvas, 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') fetch('user_log.json')
.then((res)=>{ .then((res)=>{
return res.json(); return res.json();
@ -431,7 +452,6 @@ fetch('user_log.json')
placeholder.innerHTML = out; 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(){ async function getStartValues(){
await fetch('data.json') await fetch('data.json')
.then((res) =>{ .then((res) =>{

View File

@ -71,22 +71,9 @@ h4{
font-weight: bolder; font-weight: bolder;
} }
#pre-reg{
display: block;
position: relative;
float:right;
border-radius: 5px;
}
#reg{
border-radius: 5px;
color: black;
background-color: #CCE3DE;
}
.logout{ .logout{
margin-left: 20px; margin-left: 20px;
margin-top: 35px; margin-top: 35px;
} }
#btn_log_out{ #btn_log_out{
margin-bottom: 10px; margin-bottom: 10px;
@ -104,7 +91,6 @@ h4{
border-radius: 5px; border-radius: 5px;
background-color: #CCE3DE; background-color: #CCE3DE;
border: 0.5px solid; border: 0.5px solid;
color: black;
} }
#user{ #user{
@ -303,19 +289,11 @@ canvas{
margin-bottom: 50px; margin-bottom: 50px;
width: 50%; width: 50%;
margin: auto; margin: auto;
}
#log{
height: 300px;
overflow: hidden;
overflow-y: scroll;
width: 100%;
} }
table{ table{
background-color: #CCE3DE; background-color: #CCE3DE;
width: 100%; width: 90%;
margin: auto; margin: auto;
border-collapse: collapse; border-collapse: collapse;
} }
@ -324,12 +302,6 @@ table, th, td {
border: 1px solid; border: 1px solid;
} }
.th.fixed{
top: 0;
z-index: 2;
position: sticky;
}
td{ td{
text-align: center; text-align: center;
} }

View File

@ -1,34 +1,4 @@
[ [
{
"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, "id": 15,
"UserId": "Miisa", "UserId": "Miisa",