Skip to content

Instantly share code, notes, and snippets.

@fadhilaf
Last active May 28, 2024 19:15
Show Gist options
  • Save fadhilaf/0169875e0e93944f1b41803631b44b0d to your computer and use it in GitHub Desktop.
Save fadhilaf/0169875e0e93944f1b41803631b44b0d to your computer and use it in GitHub Desktop.
Simple soil moisture sensor HTML interface + esp32 server
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#define SSID "ssid"
#define PASSWORD "password"
#define moisture_pin 35
AsyncWebServer server(80);
void setup() {
// Baud rate
Serial.begin(9600);
// Set up koneksi Wi-Fi
WiFi.begin(SSID, PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
// Set up routes
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", generateHTML());
});
// Define endpoint AJAX requests untuk tingkat kelembapan
server.on("/sensor", HTTP_GET, [](AsyncWebServerRequest *request) {
//baca sensor
int moistureLevel = analogRead(moisture_pin);
// Data response
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", String(moistureLevel));
// Add CORS header
response->addHeader("Access-Control-Allow-Origin", "*");
// Kirim
request->send(response);
});
server.begin();
}
void loop() {
Serial.println(analogRead(moisture_pin));
Serial.println(WiFi.localIP());
delay(10000);
}
String generateHTML() {
// Generate HTML content with variable values
String htmlContent = "<!doctypehtml><html lang=en><meta charset=UTF-8><meta content=\"width=device-width,initial-scale=1\"name=viewport><title>Sensor Kelembaban Tanah</title><script src=https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.js></script><link href=https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css rel=stylesheet><style>#table-container{max-height:300px;overflow-y:auto}</style><h1>Sensor Kelembaban Tanah</h1><form id=dataForm><label for=interval>Update sensor per (ms 1000 untuk 1 detik):</label> <input id=interval><br><label for=queue>Jumlah data maksimal (60 untuk menyimpan satu menit jika update sensor per 1 detik):</label> <input id=queue><br><button id=submitButton type=button>Tampilkan sensor</button></form><canvas height=100 id=myChart width=400></canvas><div id=table-container><table class=\"table table-striped\"><thead><tr><th scope=col style=width:80%>Waktu<th scope=col style=width:10%><th scope=col style=width:10%><tbody id=table-body></table></div><div id=history></div><script>async function fetchData(){const t=Date.now();try{const e=await fetch(";
htmlContent += "\"http://" + WiFi.localIP().toString() + "/sensor\"";
htmlContent += ");if(e.ok){const a=await e.json();return[t,4095-parseInt(a)]}throw new Error(\"Network response was not ok.\")}catch(t){return console.error(\"Error fetching data:\",t),[]}}document.addEventListener(\"DOMContentLoaded\",function(){const t=document.getElementById(\"interval\"),e=document.getElementById(\"queue\"),a=document.getElementById(\"dataForm\"),n=document.getElementById(\"submitButton\"),r=document.getElementById(\"myChart\");n.addEventListener(\"click\",function(){const n=t.value.trim(),o=e.value.trim();\"\"!==n&&\"\"!==o?function(t,e){let n=new Chart(r,{type:\"line\",data:createChartData([],[],\"red\")});r.style.display=\"block\",a.style.display=\"none\",setInterval(()=>run(n,e),t)}(n,o):alert(\"Mohon lengkapi data.\")}),renderTable()});let intervalState=0;function updateChartData(t,e,a){addToQueue(t.data.labels,currentTime(e[0]),a),addToQueue(t.data.datasets[0].data,e[1],a),e[1]<2048?t.data.datasets[0].borderColor=\"red\":t.data.datasets[0].borderColor=\"green\",++intervalState==a&&(saveChartState(t,e[0],a),intervalState=0),t.update()}function saveChartState(t,e){let a,n=localStorage.getItem(\"sensorData\");(a=null==n?{}:JSON.parse(n))[e]={labels:t.data.labels,data:t.data.datasets[0].data},localStorage.setItem(\"sensorData\",JSON.stringify(a)),renderTable()}function deleteChartState(t){let e=localStorage.getItem(\"sensorData\");if(null!=e){const a=JSON.parse(e);delete a[t],localStorage.setItem(\"sensorData\",JSON.stringify(a)),renderTable()}}function renderTable(){document.getElementById(\"table-body\").innerHTML=\"\";let t=localStorage.getItem(\"sensorData\");if(null!=t){const e=JSON.parse(t);Object.keys(e).forEach(t=>{createRow(parseInt(t,10))})}}function createRow(t){const e=document.createElement(\"tr\");e.innerHTML=`\\n <td>${toFullStringDate(t)}</td>\\n <td><button class=\"btn btn-primary btn-sm\">Lihat</button></td>\\n <td><button class=\"btn btn-danger btn-sm\">Hapus</button></td>\\n `,e.querySelector(\".btn-danger\").addEventListener(\"click\",function(){deleteChartState(t)}),e.querySelector(\".btn-primary\").addEventListener(\"click\",function(){renderSavedChart(t)}),document.getElementById(\"table-body\").appendChild(e)}function renderSavedChart(t){let e,a=localStorage.getItem(\"sensorData\");if(t in(e=null==a?{}:JSON.parse(a))){const a=e[t],n=document.createElement(\"canvas\");new Chart(n,{type:\"line\",data:createChartData(a.labels,a.data,checkArray(a.data,2048)?\"green\":\"red\")});const r=document.createElement(\"div\");r.className=\"border m-3 p-3\",r.innerHTML=`\\n <h3>${toFullStringDate(t)}</h3>\\n `;const o=document.createElement(\"button\");o.addEventListener(\"click\",function(){r.remove()}),o.className=\"btn btn-secondary\",o.innerText=\"Tutup\",r.appendChild(n),r.appendChild(o),document.getElementById(\"history\").appendChild(r)}}async function run(t,e){updateChartData(t,await fetchData(),e)}function addToQueue(t,e,a){t.push(e);const n=a;t.length>n&&t.shift()}function currentTime(t){const e=new Date(t);return`${e.getHours().toString().padStart(2,\"0\")}:${e.getMinutes().toString().padStart(2,\"0\")}:${e.getSeconds().toString().padStart(2,\"0\")}`}function toFullStringDate(t){const e=new Date(t),a=e.getFullYear(),n=(\"0\"+(e.getMonth()+1)).slice(-2);return`${(\"0\"+e.getDate()).slice(-2)}/${n}/${a} ${(\"0\"+e.getHours()).slice(-2)}:${(\"0\"+e.getMinutes()).slice(-2)}:${(\"0\"+e.getSeconds()).slice(-2)}`}function createChartData(t,e,a){return{type:\"line\",labels:t,datasets:[{label:\"Tingkat kelembaban tanah\",data:e,borderColor:a,backgroundColor:\"rgba(0, 0, 255, 0.1)\",tension:0}]}}function checkArray(t,e){if(!Array.isArray(t)||0===t.length)throw new Error(\"Input must be a non-empty array of integers.\");return t.filter(t=>t>e).length/t.length*100>=80}</script><script src=https://code.jquery.com/jquery-3.5.1.slim.min.js></script><script src=https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js></script><script src=https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js></script>";
return htmlContent;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sensor Kelembaban Tanah</title>
<!-- CDN Chart.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<style>
/* Styling for the table and scrollable container */
#table-container {
max-height: 300px; /* Set the maximum height of the container */
overflow-y: auto; /* Enable vertical scrolling */
}
</style>
</head>
<body>
<h1>Sensor Kelembaban Tanah</h1>
<form id="dataForm">
<label for="ip">IP ESP32:</label>
<input type="text" id="ip">
<br>
<label for="interval">Update sensor per (ms 1000 untuk 1 detik):</label>
<input type="text" id="interval">
<br>
<label for="queue">Jumlah data maksimal (60 untuk menyimpan satu menit jika update sensor per 1 detik):</label>
<input type="text" id="queue">
<br>
<button type="button" id="submitButton">Tampilkan sensor</button>
</form>
<!-- Elemen grafik -->
<canvas id="myChart" width="400" height="100"></canvas>
<!-- Elemen tabel -->
<div id="table-container">
<table class="table table-striped">
<thead>
<tr>
<th scope="col" style="width: 80%;">Waktu</th>
<th scope="col" style="width: 10%;"></th>
<th scope="col" style="width: 10%;"></th>
</tr>
</thead>
<tbody id="table-body">
<!-- Table rows will be dynamically added here -->
</tbody>
</table>
</div>
<!-- Elemen histori data -->
<div id="history"></div>
<script src="script.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>
document.addEventListener("DOMContentLoaded", function() {
const ip = document.getElementById("ip");
const interval = document.getElementById("interval");
const queue = document.getElementById("queue");
const dataForm = document.getElementById("dataForm");
const submitButton = document.getElementById("submitButton");
const ctx = document.getElementById("myChart");
submitButton.addEventListener("click", function() {
const ipData = ip.value.trim();
const intervalData = interval.value.trim();
const queueData = queue.value.trim();
if (ipData !== "" && intervalData !== "" && queueData !== "") {
renderChart(ipData, intervalData, queueData);
} else {
alert("Mohon lengkapi data.");
}
});
function renderChart(ipData, intervalData, queueData) {
let queue = [];
// Bentuk data
let myChart = new Chart(ctx, {
type: 'line',
data: createChartData([], queue, 'red'),
});
// Menampilkan grafik
ctx.style.display = "block";
dataForm.style.display = "none";
setInterval(() => run(myChart, ipData, queueData), intervalData); //set IP
}
renderTable();
});
// Memanggil endpoint ESP32
async function fetchData(ip) {
// URL ESP32
const url = "http://" + ip
const time = Date.now();
try {
const response = await fetch(url + '/sensor');
if (response.ok) {
const responseData = await response.json();
return [time, 4095 - parseInt(responseData)];
} else {
throw new Error('Network response was not ok.');
}
} catch (error) {
console.error('Error fetching data:', error);
return [];
}
}
let intervalState = 0;
function updateChartData(myChart, newData, queueData) {
addToQueue(myChart.data.labels, currentTime(newData[0]), queueData);
addToQueue(myChart.data.datasets[0].data, newData[1], queueData);
if (newData[1] < 2048) {
myChart.data.datasets[0].borderColor = 'red';
} else {
myChart.data.datasets[0].borderColor = 'green';
}
intervalState++;
if (intervalState == queueData) {
saveChartState(myChart, newData[0], queueData);
intervalState = 0;
}
myChart.update();
}
function saveChartState(chart, key) {
let currentSensorDataJson = localStorage.getItem('sensorData');
let currentDataObject;
if (currentSensorDataJson == null) {
currentDataObject = {};
}
else {
currentDataObject = JSON.parse(currentSensorDataJson);
}
currentDataObject[key] = {
labels: chart.data.labels,
data: chart.data.datasets[0].data,
}
localStorage.setItem('sensorData', JSON.stringify(currentDataObject));
renderTable();
}
function deleteChartState(key) {
let currentSensorDataJson = localStorage.getItem('sensorData');
if (currentSensorDataJson != null) {
const currentDataObject = JSON.parse(currentSensorDataJson);
delete currentDataObject[key];
localStorage.setItem('sensorData', JSON.stringify(currentDataObject));
renderTable()
}
}
function renderTable() {
document.getElementById('table-body').innerHTML = "";
let currentSensorDataJson = localStorage.getItem('sensorData');
if (currentSensorDataJson != null) {
const currentDataObject = JSON.parse(currentSensorDataJson);
Object.keys(currentDataObject).forEach(key => {
createRow(parseInt(key, 10));
})
}
}
function createRow(key) {
const newRow = document.createElement('tr');
newRow.innerHTML = `
<td>${toFullStringDate(key)}</td>
<td><button class="btn btn-primary btn-sm">Lihat</button></td>
<td><button class="btn btn-danger btn-sm">Hapus</button></td>
`;
// Add delete functionality to the new row's delete button
newRow.querySelector('.btn-danger').addEventListener('click', function() {
deleteChartState(key);
});
newRow.querySelector('.btn-primary').addEventListener('click', function() {
renderSavedChart(key);
});
// Append the new row to the table body
document.getElementById('table-body').appendChild(newRow);
}
function renderSavedChart(key) {
let currentSensorDataJson = localStorage.getItem('sensorData');
let currentDataObject;
if (currentSensorDataJson == null) {
currentDataObject = {};
}
else {
currentDataObject = JSON.parse(currentSensorDataJson);
}
if (key in currentDataObject) {
const chartData = currentDataObject[key];
const newCanvas = document.createElement('canvas');
new Chart(newCanvas, {
type: 'line',
data: createChartData(chartData.labels, chartData.data, checkArray(chartData.data, 2048) ? "green" : "red"),
});
const newDiv = document.createElement('div');
newDiv.className = 'border m-3 p-3';
newDiv.innerHTML = `
<h3>${toFullStringDate(key)}</h3>
`;
const newButton = document.createElement('button');
newButton.addEventListener('click', function() {
newDiv.remove();
});
newButton.className = "btn btn-secondary";
newButton.innerText = "Tutup";
newDiv.appendChild(newCanvas)
newDiv.appendChild(newButton)
document.getElementById('history').appendChild(newDiv)
}
}
async function run(myChart, ip, queueData) {
const newData = await fetchData(ip);
updateChartData(myChart, newData, queueData);
}
// Utility functions
// Memperbarui antrian data
function addToQueue(queue, newData, queueData) {
queue.push(newData);
const maxSize = queueData; // Muatan maksimum
if (queue.length > maxSize) {
queue.shift();
}
}
// Menghasilkan string waktu sekarang
function currentTime(timestamp) {
const date = new Date(timestamp);
let hours = date.getHours().toString().padStart(2, '0');
let minutes = date.getMinutes().toString().padStart(2, '0');
let seconds = date.getSeconds().toString().padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
}
// Menghasilkan string tanggal lengkap
function toFullStringDate(timestamp) {
const date = new Date(timestamp);
const year = date.getFullYear();
const month = ('0' + (date.getMonth() + 1)).slice(-2); // Months are zero-indexed
const day = ('0' + date.getDate()).slice(-2);
const hours = ('0' + date.getHours()).slice(-2);
const minutes = ('0' + date.getMinutes()).slice(-2);
const seconds = ('0' + date.getSeconds()).slice(-2);
return `${day}/${month}/${year} ${hours}:${minutes}:${seconds}`;
}
function createChartData(labels, data, color) {
return {
type: 'line',
labels: labels,
datasets: [{
label: 'Tingkat kelembaban tanah',
data: data,
borderColor: color,
backgroundColor: 'rgba(0, 0, 255, 0.1)',
tension: 0,
}]
};
}
function checkArray(arr, threshold) {
if (!Array.isArray(arr) || arr.length === 0) {
throw new Error("Input must be a non-empty array of integers.");
}
const countAboveThreshold = arr.filter(item => item > threshold).length;
const percentage = (countAboveThreshold / arr.length) * 100;
return percentage >= 80;
}
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#define SSID "ssid"
#define PASSWORD "pass"
#define moisture_pin 35
AsyncWebServer server(80);
void setup() {
// Baud rate
Serial.begin(9600);
// Set up koneksi Wi-Fi
WiFi.begin(SSID, PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
// Define endpoint AJAX requests untuk tingkat kelembapan
server.on("/sensor", HTTP_GET, [](AsyncWebServerRequest *request) {
//baca sensor
int moistureLevel = analogRead(moisture_pin);
// Data response
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", String(moistureLevel));
// Add CORS header
response->addHeader("Access-Control-Allow-Origin", "*");
// Kirim
request->send(response);
});
server.begin();
}
void loop() {
Serial.println(analogRead(moisture_pin));
Serial.println(WiFi.localIP());
delay(10000);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment