Skip to content
Snippets Groups Projects
Commit a76af538 authored by Vojtěch Novotný's avatar Vojtěch Novotný
Browse files

fyzické zobrazovací zařízení, darkmode, arduino code, python skript, readme

parent fbb20ab6
No related branches found
No related tags found
1 merge request!4stabilni verze semestralni prace, 3 cviceni, domaci ukol 1
...@@ -7,16 +7,22 @@ ...@@ -7,16 +7,22 @@
-todo -todo
## Semestrální práce ## Semestrální práce
Cílem mé semestrální práce je vytvořit systém, který umožňuje zobrazovat hodnoty teploty a vlhkosti z čidla DHT11 na webové stránce. Data budou sbírána pomocí ESP32 a následně odesílána na server běžící na telefonu s operačním systémem Android pomocí aplikace Termux, OpenSSH a tunnel forwarding na localhost.run. Webové rozhraní bude vytvořeno v jazyce PHP,HTML,JS a bude zobrazovat aktuální hodnoty teploty a vlhkosti v reálném čase. Cílem mé semestrální práce je vytvořit systém, který umožňuje zobrazovat hodnoty teploty a vlhkosti z čidla DHT11 na webové stránce. Data budou sbírána pomocí ESP32 a následně odesílána na server běžící na telefonu s operačním systémem Android pomocí aplikace Termux, OpenSSH a tunnel forwarding pomocí cloudflare. Webové rozhraní bude vytvořeno v jazyce PHP,HTML,JS a bude zobrazovat aktuální hodnoty teploty a vlhkosti v reálném čase.
### Progres ### Progres
Momentálně je úspěšně zprovozněn server z mobilního telefonu, a 2 ESP se senzory. Dále vyvýjím mobilní verzi webové stránky, která by se měla využít na starším tabletu/telefonu a bude sloužit jako statický, fyzický displej pro teploměr a vlhkoměr. Momentálně je úspěšně zprovozněn server z mobilního telefonu, a 2 ESP se senzory. Dále vyvýjím mobilní verzi webové stránky, která by se měla využít na starším tabletu/telefonu a bude sloužit jako statický, fyzický displej pro teploměr a vlhkoměr.
Doma jsem našel Lenovo A3500-FL s android verzí 4.2. Díky SDKManageru jsem na tablet dokázal stáhnout funkční skoro-moderní prohlížeč ViaBrowser a aplikaci Fulscrn free, obě aplikace kompatibilní s touto pradávnou verzí androidu. Kvůli stáří jsem musel vytvořít nový soubor s html a javascriptem, který bude taktéž podporován. Zatím je to testovací verze, která nevypadá ideálně, každopádně funguje.
### TO-DO ### TO-DO
- [ ] Dokoupit další hardware, ESP,DHT,16850 li baterie - [ ] Dokoupit další hardware, ESP,DHT,16850 li baterie
- [ ] Zdokonalit design webové stránky - [ ] Zdokonalit design webové stránky
- [ ] Více prozkoumat funkčnost localhost.run (velké problémy ohledně port forwardingu, protože telefon je připojen pouze k Wi-Fi a ngrok nefungoval právě kvuli wifi, serveo.net je velmi nestabilní)
- [ ] Jiné zobrazovací zařízení?
- [ ] Tisk krabiček pro obvody - [ ] Tisk krabiček pro obvody
### DONE
- [x] Dark mode
- [x] Více prozkoumat funkčnost localhost.run (velké problémy ohledně port forwardingu, protože telefon je připojen pouze k Wi-Fi a ngrok nefungoval právě kvuli wifi, serveo.net je velmi nestabilní)
- zprovoznění cloudflared
- [x] Jiné zobrazovací zařízení?
\ No newline at end of file
{
"room1": {
"temperature": 25.6,
"humidity": 49,
"timestamp": 1740608594,
"temperatures": [
24.7,
24.7,
24.7,
24.6,
24.6,
24.6,
24.5,
24.8,
24.8,
24.8,
25.4,
25.6,
26,
26.5
],
"humidities": [
39,
38,
38,
38,
38,
37,
37,
42,
42,
41,
49,
49,
48,
47
],
"timestamps": [
1740611414,
1740611474,
1740611534,
1740611594,
1740611654,
1740611714,
1740611774,
1740611874,
1740611934,
1740611994,
1740656633,
1740656835,
1740656895,
1740656955
]
},
"room2": {
"temperature": 26.3,
"humidity": 44,
"timestamp": 1740608595,
"temperatures": [
26.3,
26.3,
26.3,
26.3,
26.3,
26.1,
25.5,
25,
24.7,
24.6
],
"humidities": [
36,
35,
35,
35,
35,
35,
35,
36,
37,
38
],
"timestamps": [
1740611475,
1740611535,
1740611595,
1740611655,
1740611715,
1740611775,
1740611835,
1740611895,
1740611955,
1740612015
]
},
"room3": {
"temperature": 25.6,
"humidity": 49,
"timestamp": 1740608594,
"temperatures": [
24.7,
24.7,
24.7,
24.6,
24.6,
24.6,
24.5,
24.8,
24.8,
24.8,
25.4,
25.6,
26,
26.5
],
"humidities": [
39,
38,
38,
38,
38,
37,
37,
42,
42,
41,
49,
49,
48,
47
],
"timestamps": [
1740611414,
1740611474,
1740611534,
1740611594,
1740611654,
1740611714,
1740611774,
1740611874,
1740611934,
1740611994,
1740656633,
1740656835,
1740656895,
1740656955
]
}
}
\ No newline at end of file
STOP TMUX (ctrl+b, D)
tmux attach -t keep_web_alive
\ No newline at end of file
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
...@@ -7,24 +8,137 @@ ...@@ -7,24 +8,137 @@
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style> <style>
.sensor-card { margin-bottom: 20px; } body {
.reading { font-size: 2rem; font-weight: bold; } background-color: #f8f9fa;
.timestamp { font-size: 0.8rem; color: #666; } color: #212529;
.chart-container { height: 150px; margin-top: 10px; } transition: background-color 0.3s, color 0.3s;
.phone-view .reading { font-size: 4rem; } }
.phone-view .chart-container, .phone-view .timestamp { display: none; }
.phone-view .sensor-card { display: inline-block; width: 48%; margin-right: 2%; } body.dark-mode {
.phone-view .sensor-card:nth-child(2n) { margin-right: 0; } background-color: #121212;
.editable-title { cursor: pointer; } color: #f8f9fa;
.editable-title input { display: none; width: 100%; } }
.sensor-card {
margin-bottom: 20px;
}
.reading {
font-size: 2rem;
font-weight: bold;
}
.timestamp {
font-size: 0.8rem;
color: #666;
}
.chart-container {
height: 150px;
margin-top: 10px;
}
.phone-view .reading {
font-size: 4rem;
}
.phone-view .chart-container,
.phone-view .timestamp {
display: none;
}
.phone-view .sensor-card {
display: inline-block;
width: 48%;
margin-right: 2%;
}
.phone-view .sensor-card:nth-child(2n) {
margin-right: 0;
}
.editable-title {
cursor: pointer;
}
.editable-title input {
display: none;
width: 100%;
}
.card {
background-color: #ffffff;
border: 1px solid #dee2e6;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s, border-color 0.3s;
}
body.dark-mode .card {
background-color: #1e1e1e;
border-color: #333;
}
.card-header {
background-color: #212529;
color: #ffffff;
border-bottom: none;
border-radius: 10px 10px 0 0;
}
body.dark-mode .card-header {
background-color: #212529;
}
.btn-primary {
background-color: #212529;
border-color: #212529;
}
body.dark-mode .btn-primary {
background-color: #212529;
border-color: #212529;
}
.alert {
background-color: #fff3cd;
border-color: #ffeeba;
color: #856404;
}
body.dark-mode .alert {
background-color: #332d00;
border-color: #665c00;
color: #ffd700;
}
.text-danger {
color: #dc3545 !important;
}
body.dark-mode .text-danger {
color: #ff6b6b !important;
}
.dark-mode-toggle {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}
</style> </style>
</head> </head>
<body> <body>
<div class="container mt-4"> <div class="container mt-4">
<button id="toggle-dark-mode" class="btn btn-secondary dark-mode-toggle">Toggle Dark Mode</button>
<h1 class="mb-4 text-center">Monitoring Temperature & Humidity</h1> <h1 class="mb-4 text-center">Monitoring Temperature & Humidity</h1>
<hr>
</hr>
<button id="toggle-phone-view" class="btn btn-primary mb-4">Toggle Phone View</button> <button id="toggle-phone-view" class="btn btn-primary mb-4">Toggle Phone View</button>
<div class="row" id="sensor-data"></div> <div class="row" id="sensor-data"></div>
</div> </div>
<script src="script.js"></script> <script src="script.js"></script>
</body> </body>
</html> </html>
\ No newline at end of file
<?php <?php
// File to store sensor data as JSON // File to store sensor data as JSON
$dataFile = 'sensor_data.json'; $dataFile = 'semestralni_prace/data/sensor_data.json';
// File to store custom room names as JSON
$namesFile = 'data/room_names.json';
// Check if this is a data submission // Check if this is a data submission
if (isset($_GET['p']) && $_GET['p'] == 'data') { if (isset($_GET['p']) && $_GET['p'] == 'data') {
...@@ -50,102 +52,19 @@ if (isset($_GET['p']) && $_GET['p'] == 'data') { ...@@ -50,102 +52,19 @@ if (isset($_GET['p']) && $_GET['p'] == 'data') {
// If we're getting data // If we're getting data
if (isset($_GET['p']) && $_GET['p'] == 'get') { if (isset($_GET['p']) && $_GET['p'] == 'get') {
header('Content-Type: application/json'); header('Content-Type: application/json');
echo file_exists($dataFile) ? file_get_contents($dataFile) : '{}'; $data = [
'sensorData' => file_exists($dataFile) ? json_decode(file_get_contents($dataFile), true) : [],
'roomNames' => file_exists($namesFile) ? json_decode(file_get_contents($namesFile), true) : []
];
echo json_encode($data);
exit; exit;
} }
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Monitoring Temperature & Humidity</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.sensor-card { margin-bottom: 20px; }
.reading { font-size: 2rem; font-weight: bold; }
.timestamp { font-size: 0.8rem; color: #666; }
.chart-container { height: 150px; margin-top: 10px; }
</style>
</head>
<body>
<div class="container mt-4">
<h1 class="mb-4">Monitoring Temperature & Humidity</h1>
<div class="row" id="sensor-data"></div>
</div>
<script>
function formatTimestamp(timestamp) {
return new Date(timestamp * 1000).toLocaleString();
}
function loadSensorData() {
fetch('index.php?p=get')
.then(response => response.json())
.then(data => {
const sensorContainer = document.getElementById('sensor-data');
sensorContainer.innerHTML = '';
if (Object.keys(data).length === 0) {
sensorContainer.innerHTML = '<div class="col-12"><div class="alert alert-warning">No sensor data available yet.</div></div>';
return;
}
let html = '';
for (const [room, readings] of Object.entries(data)) {
const roomName = room.charAt(0).toUpperCase() + room.slice(1);
const ageInMinutes = Math.floor((Date.now() / 1000 - readings.timestamps[readings.timestamps.length - 1]) / 60);
html += `
<div class="col-md-6">
<div class="card sensor-card">
<div class="card-header bg-primary text-white">${roomName}</div>
<div class="card-body">
<div class="reading text-center">${readings.temperatures.at(-1).toFixed(1)}°C</div>
<div class="reading text-center">${readings.humidities.at(-1).toFixed(1)}%</div>
<div class="timestamp text-center">Last updated: ${formatTimestamp(readings.timestamps.at(-1))}
${ageInMinutes > 5 ? `<div class="text-danger">(${ageInMinutes} minutes ago)</div>` : ''}
</div>
<div class="chart-container"><canvas id="temp-chart-${room}"></canvas></div>
<div class="chart-container"><canvas id="hum-chart-${room}"></canvas></div>
</div>
</div>
</div>`;
}
sensorContainer.innerHTML = html;
for (const [room, readings] of Object.entries(data)) {
renderChart(`temp-chart-${room}`, readings.temperatures, readings.timestamps, 'Temperature (°C)', 'rgba(255, 99, 132, 1)');
renderChart(`hum-chart-${room}`, readings.humidities, readings.timestamps, 'Humidity (%)', 'rgba(54, 162, 235, 1)');
}
})
.catch(error => {
console.error('Error loading sensor data:', error);
document.getElementById('sensor-data').innerHTML = '<div class="col-12"><div class="alert alert-danger">Error loading sensor data. Please try again later.</div></div>';
});
}
function renderChart(canvasId, dataPoints, timestamps, label, color) { // If we're saving room names
new Chart(document.getElementById(canvasId).getContext('2d'), { if (isset($_POST['p']) && $_POST['p'] == 'saveNames') {
type: 'line', $roomNames = json_decode(file_get_contents('php://input'), true);
data: { file_put_contents($namesFile, json_encode($roomNames));
labels: timestamps.map(t => formatTimestamp(t)), echo "Room names saved successfully";
datasets: [{ exit;
label: label, }
data: dataPoints, ?>
borderColor: color, \ No newline at end of file
backgroundColor: color.replace('1)', '0.2)'),
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: { x: { display: false }, y: { beginAtZero: false } },
plugins: { legend: { display: false } }
}
});
}
loadSensorData();
setInterval(loadSensorData, 30000);
</script>
</body>
</html>
\ No newline at end of file
import time
import requests
from datetime import datetime
def access_website(url):
try:
response = requests.get(url)
status = f"Accessed {url} - Status Code: {response.status_code}"
print(status)
return status
except requests.exceptions.RequestException as e:
error = f"Error accessing {url}: {e}"
print(error)
return error
def log_access(log_file, message):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(log_file, "a") as file:
file.write(f"[{timestamp}] {message}\n")
def main():
url = "https://web-calculators-dsl-activation.trycloudflare.com/docs/thproject/index.html" # Replace with the URL you want to access
interval = 600 # 10 minutes in seconds
log_file = "website_access.log" # Log file name
while True:
result = access_website(url)
log_access(log_file, result)
time.sleep(interval)
if __name__ == "__main__":
main()
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Temperature & Humidity Monitor</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
color: #333;
margin: 0;
padding: 20px;
text-align: center;
}
h1 {
font-size: 24px;
margin-bottom: 20px;
}
.data {
font-size: 18px;
margin: 10px 0;
}
#status {
color: #666;
font-size: 14px;
margin: 10px 0;
}
</style>
</head>
<body>
<h1>Temperature & Humidity Monitor</h1>
<div id="sensor-data">
Loading data...
</div>
<div id="status"></div>
<script>
// Function to format date for older browsers
function formatDate(timestamp) {
var date = new Date(timestamp * 1000);
var day = date.getDate();
var month = date.getMonth() + 1;
var year = date.getFullYear();
var hours = date.getHours();
var minutes = date.getMinutes();
// Add leading zeros
if (day < 10) day = '0' + day;
if (month < 10) month = '0' + month;
if (hours < 10) hours = '0' + hours;
if (minutes < 10) minutes = '0' + minutes;
return day + '/' + month + '/' + year + ' ' + hours + ':' + minutes;
}
// Function to fetch and display data
function fetchData() {
var statusElement = document.getElementById('status');
statusElement.innerHTML = "Fetching data...";
var xhr = new XMLHttpRequest();
// Handle errors properly
xhr.onerror = function () {
statusElement.innerHTML = "Error loading data. Please check your connection.";
};
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
var data = JSON.parse(xhr.responseText);
var sensorData = document.getElementById('sensor-data');
var html = '';
// Loop through each room and display data
for (var room in data) {
if (data.hasOwnProperty(room)) {
html += '<div class="data">' +
'<strong>' + room + '</strong><br>' +
'Temperature: ' + data[room].temperature + ' °C<br>' +
'Humidity: ' + data[room].humidity + ' %<br>' +
'Last updated: ' + formatDate(data[room].timestamp) +
'</div><hr>';
}
}
if (html === '') {
sensorData.innerHTML = "No data available";
} else {
sensorData.innerHTML = html;
}
statusElement.innerHTML = "Last updated: " + new Date().toLocaleTimeString();
} catch (e) {
statusElement.innerHTML = "Error parsing data: " + e.message;
}
} else {
statusElement.innerHTML = "Error loading data. Status: " + xhr.status;
}
}
};
// Use a cache-busting parameter to avoid caching issues on older browsers
xhr.open('GET', 'data/sensor_data.json?_=' + new Date().getTime(), true);
xhr.send();
}
// Store the interval ID
var dataInterval;
// Function to start the interval
function startDataRefresh() {
// Clear any existing interval first
if (dataInterval) {
clearInterval(dataInterval);
}
// Fetch data immediately
fetchData();
// Then set up the interval
dataInterval = setInterval(fetchData, 5000);
}
// Start the data refresh when page loads
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', startDataRefresh);
} else {
// For older browsers
window.onload = startDataRefresh;
}
</script>
</body>
</html>
\ No newline at end of file
...@@ -22,7 +22,7 @@ function loadSensorData() { ...@@ -22,7 +22,7 @@ function loadSensorData() {
html += ` html += `
<div class="col-md-6 sensor-card"> <div class="col-md-6 sensor-card">
<div class="card"> <div class="card">
<div class="card-header bg-primary text-white text-center editable-title" onclick="editTitle(this)"> <div class="card-header text-white text-center editable-title" onclick="editTitle(this)" color="#212529">
<span>${roomName}</span> <span>${roomName}</span>
<input type="text" value="${roomName}" onblur="saveTitle(this, '${room}')" onkeypress="handleKeyPress(event, this, '${room}')"> <input type="text" value="${roomName}" onblur="saveTitle(this, '${room}')" onkeypress="handleKeyPress(event, this, '${room}')">
</div> </div>
...@@ -113,9 +113,26 @@ function saveRoomNames() { ...@@ -113,9 +113,26 @@ function saveRoomNames() {
}); });
} }
//phone view
document.getElementById('toggle-phone-view').addEventListener('click', () => { document.getElementById('toggle-phone-view').addEventListener('click', () => {
document.body.classList.toggle('phone-view'); document.body.classList.toggle('phone-view');
}); });
document.getElementById('toggle-dark-mode').addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
const isDarkMode = document.body.classList.contains('dark-mode');
localStorage.setItem('darkMode', isDarkMode);
document.getElementById('toggle-dark-mode').textContent = isDarkMode ? '☀️' : '🌙';
});
// Check for saved dark mode preference
if (localStorage.getItem('darkMode') === 'true') {
document.body.classList.add('dark-mode');
document.getElementById('toggle-dark-mode').textContent = '☀️';
} else {
document.body.classList.remove('dark-mode');
document.getElementById('toggle-dark-mode').textContent = '🌙';
}
loadSensorData(); loadSensorData();
setInterval(loadSensorData, 30000); setInterval(loadSensorData, 30000);
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment