Commit 12/08/2025

This commit is contained in:
2025-08-12 16:48:36 +07:00
parent 60e8524c8f
commit 8c32e48e04
14 changed files with 970 additions and 21 deletions

View File

@@ -0,0 +1,109 @@
$(document).ready(function() {
const path = window.location.pathname;
const ws = new WebSocket('ws://' + window.location.host + path + '/ws');
ws.onopen = function() {
//console.log('WebSocket connection opened');
$('#indicatorDisconnected').addClass('visually-hidden');
$('#indicatorConnected').removeClass('visually-hidden');
setInterval(function() {
sendCommand({ command: "getCPUStatus" });
sendCommand({ command: "getMemoryStatus" });
sendCommand({ command: "getDiskStatus" });
sendCommand({ command: "getEthernetStatus" });
sendCommand({ command: "getModemStatus" });
sendCommand({ command: "getTunnelStatus" });
}, 1000);
};
ws.onmessage = function(event) {
//console.log('WebSocket message received:', event.data);
let msg = {};
try {
msg = JSON.parse(event.data);
} catch (e) {
console.error('Invalid JSON:', event.data);
return;
}
if (msg.reply && msg.reply.length>0 && msg.data && msg.data.length > 0) {
switch (msg.reply) {
case "getCPUStatus":
const cpuData = JSON.parse(msg.data);
//console.log('CPU Status Data:', cpuData);
$('#cpuUsage').text(cpuData.average ? cpuData.average + '%' : 'N/A');
$('#core0Usage').text(cpuData.core0 ? cpuData.core0 + '%' : 'N/A');
$('#core1Usage').text(cpuData.core1 ? cpuData.core1 + '%' : 'N/A');
$('#core2Usage').text(cpuData.core2 ? cpuData.core2 + '%' : 'N/A');
$('#core3Usage').text(cpuData.core3 ? cpuData.core3 + '%' : 'N/A');
$('#cpuTemperature').text(cpuData.temperature ? cpuData.temperature + '°C' : 'N/A');
break;
case "getMemoryStatus":
const memoryData = JSON.parse(msg.data);
//console.log('Memory Status Data:', memoryData);
$('#ramTotal').text(memoryData.total ? memoryData.total : 'N/A');
$('#ramUsed').text(memoryData.used ? memoryData.used : 'N/A');
$('#ramFree').text(memoryData.free ? memoryData.free : 'N/A');
$('#ramFreePercent').text(memoryData.freePercent ? memoryData.freePercent + '%' : 'N/A');
break;
case "getDiskStatus":
const diskData = JSON.parse(msg.data);
//console.log('Disk Status Data:', diskData);
$('#diskTotal').text(diskData.total ? diskData.total : 'N/A');
$('#diskUsed').text(diskData.used ? diskData.used : 'N/A');
$('#diskFree').text(diskData.free ? diskData.free : 'N/A');
$('#diskFreePercent').text(diskData.freePercent ? diskData.freePercent + '%' : 'N/A');
break;
case "getEthernetStatus":
const ethernetData = JSON.parse(msg.data);
//console.log('Ethernet Status Data:', ethernetData);
$('#eth0Status').text(ethernetData.status ? ethernetData.status : 'N/A');
$('#eth0IP').text(ethernetData.ip ? ethernetData.ip : 'N/A');
$('#eth0TXBytes').text(ethernetData.txBytes ? ethernetData.txBytes : 'N/A');
$('#eth0RXBytes').text(ethernetData.rxBytes ? ethernetData.rxBytes : 'N/A');
$('#eth0TXSpeed').text(ethernetData.txSpeed ? ethernetData.txSpeed : 'N/A');
$('#eth0RXSpeed').text(ethernetData.rxSpeed ? ethernetData.rxSpeed : 'N/A');
break;
case "getModemStatus":
const modemData = JSON.parse(msg.data);
//console.log('Modem Status Data:', modemData);
$('#modemStatus').text(modemData.status ? modemData.status : 'N/A');
$('#modemIP').text(modemData.ip ? modemData.ip : 'N/A');
$('#modemTXBytes').text(modemData.txBytes ? modemData.txBytes : 'N/A');
$('#modemRXBytes').text(modemData.rxBytes ? modemData.rxBytes : 'N/A');
$('#modemTXSpeed').text(modemData.txSpeed ? modemData.txSpeed : 'N/A');
$('#modemRXSpeed').text(modemData.rxSpeed ? modemData.rxSpeed : 'N/A');
break;
case "getTunnelStatus":
const tunnelData = JSON.parse(msg.data);
//console.log('Tunnel Status Data:', tunnelData);
$('#tunnelStatus').text(tunnelData.status ? tunnelData.status : 'N/A');
$('#tunnelIP').text(tunnelData.ip ? tunnelData.ip : 'N/A');
$('#tunnelTXBytes').text(tunnelData.txBytes ? tunnelData.txBytes : 'N/A');
$('#tunnelRXBytes').text(tunnelData.rxBytes ? tunnelData.rxBytes : 'N/A');
$('#tunnelTXSpeed').text(tunnelData.txSpeed ? tunnelData.txSpeed : 'N/A');
$('#tunnelRXSpeed').text(tunnelData.rxSpeed ? tunnelData.rxSpeed : 'N/A');
break;
}
}
};
ws.onclose = function() {
//console.log('WebSocket connection closed');
$('#indicatorDisconnected').removeClass('visually-hidden');
$('#indicatorConnected').addClass('visually-hidden');
};
ws.onerror = function(error) {
console.error('WebSocket error:', error);
};
function sendCommand(command) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(command));
} else {
console.error('WebSocket is not open. Unable to send command:', command);
}
}
});

View File

@@ -0,0 +1,260 @@
<!DOCTYPE html>
<html data-bs-theme="light" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>EWS_POC</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/Navbar-With-Button-icons.css">
</head>
<body>
<nav class="navbar navbar-expand-md bg-body py-3">
<div class="container"><a class="navbar-brand d-flex align-items-center" href="#"><span class="bs-icon-sm bs-icon-rounded bs-icon-primary d-flex justify-content-center align-items-center me-2 bs-icon"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-bezier">
<path fill-rule="evenodd" d="M0 10.5A1.5 1.5 0 0 1 1.5 9h1A1.5 1.5 0 0 1 4 10.5v1A1.5 1.5 0 0 1 2.5 13h-1A1.5 1.5 0 0 1 0 11.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm10.5.5A1.5 1.5 0 0 1 13.5 9h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zM6 4.5A1.5 1.5 0 0 1 7.5 3h1A1.5 1.5 0 0 1 10 4.5v1A1.5 1.5 0 0 1 8.5 7h-1A1.5 1.5 0 0 1 6 5.5zM7.5 4a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5z"></path>
<path d="M6 4.5H1.866a1 1 0 1 0 0 1h2.668A6.517 6.517 0 0 0 1.814 9H2.5c.123 0 .244.015.358.043a5.517 5.517 0 0 1 3.185-3.185A1.503 1.503 0 0 1 6 5.5zm3.957 1.358A1.5 1.5 0 0 0 10 5.5v-1h4.134a1 1 0 1 1 0 1h-2.668a6.517 6.517 0 0 1 2.72 3.5H13.5c-.123 0-.243.015-.358.043a5.517 5.517 0 0 0-3.185-3.185z"></path>
</svg></span><span>Early Warning System Receiver</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navcol-1">
<ul class="navbar-nav me-auto">
<li class="nav-item"><a class="nav-link" href="pocreceiver.html">POC Receiver</a></li>
<li class="nav-item"><a class="nav-link" href="prerecordedbroadcast.html">Pre-Recorded Broadcast</a></li>
<li class="nav-item"><a class="nav-link" href="setting.html">Setting and Content</a></li>
<li class="nav-item"><a class="nav-link active" href="#">Hardware Status</a></li>
</ul><a class="btn btn-primary" role="button" href="index.html">Logout<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" class="visually-hidden" id="indicatorDisconnected" style="color: var(--bs-danger);width: 26px;height: 26px;font-weight: bold;">
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
<path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z"></path>
</svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" class="visually-hidden" id="indicatorConnected" style="color: var(--bs-success);width: 26px;height: 26px;font-weight: bold;">
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
<path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-111 111-47-47c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l64 64c9.4 9.4 24.6 9.4 33.9 0L369 209z"></path>
</svg></a>
</div>
</div>
</nav>
<div class="container">
<div class="row">
<h1 class="text-center">Hardware Status</h1>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">CPU (Average)</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">C0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">C1</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">C2</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">C3</p>
</div>
<div class="col-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">Temp</p>
</div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="cpuUsage">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="core0Usage">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="core1Usage">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="core2Usage">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="core3Usage">0.0</p>
</div>
<div class="col-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="cpuTemp">0</p>
</div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">RAM Total</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">Free</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">Used</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">Free %</p>
</div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="ramTotal">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="ramFree">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="ramUsed">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="ramFreePercent">0.0</p>
</div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">Disk Total</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">Free</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">Used</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">Free %</p>
</div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="diskTotal">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="diskFree">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="diskUsed">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="diskFreePercent">0.0</p>
</div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">ETH 0 Status</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">IP</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">TX Bytes</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">RX Bytes</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">TX Speed</p>
</div>
<div class="col-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">RX Speed</p>
</div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="eth0Status">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="eth0IP">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="eth0TXBytes">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="eth0RXBytes">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="eth0TXSpeed">0.0</p>
</div>
<div class="col-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="eth0RXSpeed">0</p>
</div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">Modem Status</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">IP</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">TX Bytes</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">RX Bytes</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">TX Speed</p>
</div>
<div class="col-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">RX Speed</p>
</div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="modemStatus">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="modemIP">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="modemTXBytes">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="modemRXBytes">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="modemTXSpeed">0.0</p>
</div>
<div class="col-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="modemRXSpeed">0</p>
</div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">Tunnel Status</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">IP</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">TX Bytes</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">RX Bytes</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">TX Speed</p>
</div>
<div class="col-2 col-md-2">
<p class="fw-bold d-flex h-100 justify-content-center align-items-center">RX Speed</p>
</div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="tunnelStatus">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="tunnelIP">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="tunnelTXBytes">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="tunnelRXBytes">0.0</p>
</div>
<div class="col-1 col-sm-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="tunnelTXSpeed">0.0</p>
</div>
<div class="col-2 col-md-2">
<p class="d-flex h-100 justify-content-center align-items-center" id="tunnelRXSpeed">0</p>
</div>
</div>
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/jquery-3.7.1.min.js"></script>
<script src="assets/js/hardwarestatus.js"></script>
</body>
</html>

View File

@@ -20,6 +20,7 @@
<li class="nav-item"><a class="nav-link active" href="#">POC Receiver</a></li>
<li class="nav-item"><a class="nav-link" href="prerecordedbroadcast.html">Pre-Recorded Broadcast</a></li>
<li class="nav-item"><a class="nav-link" href="setting.html">Setting and Content</a></li>
<li class="nav-item"><a class="nav-link" href="hardwarestatus.html">Hardware Status</a></li>
</ul><a class="btn btn-primary" role="button" href="index.html">Logout<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" class="visually-hidden" id="indicatorDisconnected" style="color: var(--bs-danger);width: 26px;height: 26px;font-weight: bold;">
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
<path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z"></path>
@@ -35,7 +36,7 @@
<h1 class="text-center">Zello Status</h1>
</div>
<div class="row">
<p class="d-flex justify-content-center" id="zelloStatus">Paragraph</p>
<p class="d-flex justify-content-center" id="zelloStatus">No Status</p>
</div>
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>

View File

@@ -20,6 +20,7 @@
<li class="nav-item"><a class="nav-link" href="pocreceiver.html">POC Receiver</a></li>
<li class="nav-item"><a class="nav-link active" href="#">Pre-Recorded Broadcast</a></li>
<li class="nav-item"><a class="nav-link" href="setting.html">Setting and Content</a></li>
<li class="nav-item"><a class="nav-link" href="hardwarestatus.html">Hardware Status</a></li>
</ul><a class="btn btn-primary" role="button" href="index.html">Logout<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" class="visually-hidden" id="indicatorDisconnected" style="color: var(--bs-danger);width: 26px;height: 26px;font-weight: bold;">
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
<path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z"></path>

View File

@@ -20,6 +20,7 @@
<li class="nav-item"><a class="nav-link" href="pocreceiver.html">POC Receiver</a></li>
<li class="nav-item"><a class="nav-link" href="prerecordedbroadcast.html">Pre-Recorded Broadcast</a></li>
<li class="nav-item"><a class="nav-link active" href="#">Setting and Content</a></li>
<li class="nav-item"><a class="nav-link" href="hardwarestatus.html">Hardware&nbsp; Status</a></li>
</ul><a class="btn btn-primary" role="button" href="index.html">Logout<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" class="visually-hidden" id="indicatorDisconnected" style="color: var(--bs-danger);width: 26px;height: 26px;font-weight: bold;">
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
<path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z"></path>
@@ -168,14 +169,9 @@
<div class="row w-100">
<h1 class="text-center">Content Upload</h1>
</div>
<div class="row"><input type="file" id="chosenFile"></div>
<div class="row">
<div class="col">
<div class="progress h-100" id="contentUploadProgress">
<div class="progress-bar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 50%;">50%</div>
</div>
</div>
<div class="col-2"><button class="btn btn-primary mt-2 mb-2" id="btnUploadContent" type="button">Upload</button></div>
<div class="row w-100">
<div class="col-10"><input type="file" id="chosenFile"></div>
<div class="col-2"><button class="btn btn-primary" id="btnUploadContent" type="button">Upload</button></div>
</div>
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>

View File

@@ -12,32 +12,110 @@ import zello.ZelloEvent
import javafx.util.Pair
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
import sbc.DigitalOutput
import sbc.NanopiDuo2
import sbc.SbcInfo
import sbc.cpuinfo
import sbc.meminfo
import sbc.netdev
import somecodes.Codes
import somecodes.Codes.Companion.isLinux
import web.WsCommand
import java.util.function.BiFunction
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
fun main() {
// change when updating the application
val appversion = "0.1.1"
val logger = LoggerFactory.getLogger("Main")
val objectMapper = jacksonObjectMapper()
logger.info("Application started, version 0.0.8")
logger.info("Application started, version $appversion")
val cfg = configFile()
cfg.Load()
/**
* Coroutine scope for the application
*/
val appscope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
/**
* various SBC related functions and information
*/
var sbc : SbcInfo?
/**
* cpu information, one time read
*/
var cpuinfo: cpuinfo?
/**
* ram information, will be updated periodically in appscope
*/
var raminfo: meminfo?
/**
* cpu statistics, will be updated periodically in appscope
* key is cpu name
* value is cpu utilization percentage
* e.g. cpustat["cpu0"] = 12.5
*/
val cpustat = HashMap<String, Double>()
/**
* network statistics, will be updated periodically in appscope
* key is network interface name
* value is a Pair of strings, first is RX transfer speed per second, second is TX transfer speed per second
*/
val networkstat = HashMap<String, Pair<String, String>>()
if (isLinux()){
sbc = SbcInfo()
cpuinfo = sbc.getCpuInfo()
//logger.info("CPU Info: ${cpuinfo?.hardware?:"N/A"}, Cores: ${cpuinfo?.core?:"N/A"}, SerialNumber: ${cpuinfo?.serialNumber?:"N/A"}")
appscope.launch {
// run while the application is active
while(isActive){
raminfo = sbc.getMemInfo()
//logger.info(raminfo?.toString() ?: "Failed to get RAM info")
sbc.getCpuUtilization { it ->
// result will come 1 second later, as array of pairs,
// with first being cpu name and second being utilization percentage
it.forEach { cpuinfo ->
cpustat[cpuinfo.first] = cpuinfo.second
}
//logger.info("CPU Utilization: ${cpustat.map { "${it.key.ifBlank { "avg" }}: ${ it.value}%" }}")
}
sbc.getNetworkTransferSpeed { it ->
// result will come 1 second later, as map of network interface name to Pair of RX and TX speeds
it.forEach { xx ->
networkstat[xx.key] = Pair(
netdev.toText(xx.value.first)+ "/s",
netdev.toText(xx.value.second) + "/s"
)
}
//logger.info("Network Transfer Speed: ${networkstat.map { "${it.key}: RX=${it.value.key}, TX=${it.value.value}" }}")
}
delay(5000) // Update every 5 seconds
}
}
}
var relay1 : DigitalOutput? = null
var relay2 : DigitalOutput? = null
var commandLED : DigitalOutput? = null
var streamingLED : DigitalOutput? = null
runBlocking {
appscope.launch {
relay1 = DigitalOutput(NanopiDuo2.CLK.linuxGpio, "Relay1", true)
relay2 = DigitalOutput(NanopiDuo2.MISO.linuxGpio, "Relay2", true)
commandLED = DigitalOutput(NanopiDuo2.TX1.linuxGpio, "CommandLED", true)
@@ -48,7 +126,6 @@ fun main() {
streamingLED.setOFF()
}
var audioID = 0
val preferedAudioDevice = "USB Audio"
AudioUtility.LoadLibraries()
@@ -147,12 +224,13 @@ fun main() {
relay1?.setOFF()
relay2?.setOFF()
val e = this
CoroutineScope(Dispatchers.Default).launch {
appscope.launch {
delay(10000) // Wait for 10 seconds before trying to reconnect
z = CreateZelloFromConfig()
z.Start(e)
}
}
override fun onError(errorMessage: String) {

39
src/sbc/RaspberryPi4.kt Normal file
View File

@@ -0,0 +1,39 @@
package sbc
/**
* Raspberry Pi 4 pin and GPIO mapping.
* Source : https://pinout.xyz/#
*/
@Suppress("unused")
enum class RaspberryPi4(val pin: Int, val linuxGpio: Int, val alias:String? = null) {
GPIO2(3, 2, "SDA1"),
GPIO3(5, 3, "SCL1"),
GPIO4(7, 4, "GPCLK0"),
GPIO17(11, 17),
GPIO27(13, 27),
GPIO10(19, 10, "MOSI"),
GPIO9(21, 9, "MISO"),
GPIO11(23, 11, "SCLK"),
GPIO0(27, 0,"EEPROM SDA"),
GPIO5(29, 5),
GPIO6(31, 6),
GPIO13(33, 13,"PWM1"),
GPIO19(35, 19, "PCM FS"),
GPIO26(37, 26),
GPIO14(8, 14, "TXD"),
GPIO15(10, 15, "RXD"),
GPIO18(12, 18, "PCM CLK"),
GPIO23(16, 23),
GPIO24(18, 24),
GPIO25(22, 25),
GPIO8(24, 8,"SPI CE0"),
GPIO7(26, 7, "SPI CE1"),
GPIO1(28, 1, "EEPROM SCL"),
GPIO12(32, 12, "PWM0"),
GPIO16(36, 16),
GPIO20(38, 20,"PCM DIN"),
GPIO21(40, 21, "PCM DOUT"),;
}

300
src/sbc/SbcInfo.kt Normal file
View File

@@ -0,0 +1,300 @@
package sbc
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.slf4j.LoggerFactory
import somecodes.Codes.Companion.isLinux
import java.io.File
import java.util.function.Consumer
import kotlin.io.path.Path
import kotlin.io.path.exists
import kotlin.io.path.readText
/**
* get SBC Information
*/
@Suppress("unused")
class SbcInfo {
private val logger = LoggerFactory.getLogger("SbcInfo")
/**
* Get CPU information from /proc/cpuinfo on Linux systems.
* @return cpuinfo data class containing core count, hardware model, and serial number.
* Returns null if the platform is not Linux or if an error occurs while reading the file
*/
fun getCpuInfo(): cpuinfo? {
return try {
if (isLinux()) {
val lines = File("/proc/cpuinfo").readLines()
if (lines.isNotEmpty()) {
val core = lines.count { it.startsWith("processor") }
val hardware =
lines.firstOrNull { it.startsWith("Hardware") }?.substringAfter(":")?.trim() ?: "Unknown"
val serialNumber =
lines.firstOrNull { it.startsWith("Serial") }?.substringAfter(":")?.trim() ?: "Unknown"
cpuinfo(core, hardware, serialNumber)
} else {
logger.error("No CPU info found in /proc/cpuinfo")
null
}
} else {
logger.error("Platform is not Linux, cannot get CPU info")
null
}
} catch (e: Exception) {
logger.error("Error getting CPU info: ${e.message}")
null
}
}
/**
* Get memory information from /proc/meminfo on Linux systems.
* @return meminfo data class containing total, free, available memory, buffers, cached memory, and swap info.
* Returns null if the platform is not Linux or if an error occurs while reading the file
*/
fun getMemInfo(): meminfo? {
return try {
if (isLinux()) {
val memInfoFile = File("/proc/meminfo")
if (memInfoFile.exists()) {
val lines = memInfoFile.readLines()
val totalMem =
lines.firstOrNull { it.startsWith("MemTotal:") }?.substringAfter(":")?.trim()?.split(" ")
?.first()?.toLongOrNull() ?: 0L
val freeMem =
lines.firstOrNull { it.startsWith("MemFree:") }?.substringAfter(":")?.trim()?.split(" ")
?.first()?.toLongOrNull() ?: 0L
val availableMem =
lines.firstOrNull { it.startsWith("MemAvailable:") }?.substringAfter(":")?.trim()?.split(" ")
?.first()?.toLongOrNull() ?: 0L
val buffers =
lines.firstOrNull { it.startsWith("Buffers:") }?.substringAfter(":")?.trim()?.split(" ")
?.first()?.toLongOrNull() ?: 0L
val cached =
lines.firstOrNull { it.startsWith("Cached:") }?.substringAfter(":")?.trim()?.split(" ")?.first()
?.toLongOrNull() ?: 0L
val swapTotal =
lines.firstOrNull { it.startsWith("SwapTotal:") }?.substringAfter(":")?.trim()?.split(" ")
?.first()?.toLongOrNull() ?: 0L
val swapFree = lines.firstOrNull { it.startsWith("SwapFree:") }
meminfo(
totalMem,
freeMem,
availableMem,
buffers,
cached,
swapTotal,
swapFree?.substringAfter(":")?.trim()?.split(" ")?.first()?.toLongOrNull() ?: 0L
)
} else {
logger.error("/proc/meminfo does not exist")
null
}
} else {
logger.error("Platform is not Linux, cannot get memory info")
null
}
} catch (e: Exception) {
logger.error("Error getting memory info: ${e.message}")
null
}
}
/**
* Get CPU utilization percentage for each CPU core.
* @param percent Consumer that accepts an array of pairs containing CPU core names and their utilization percentages.
* This function runs in a coroutine and retrieves CPU usage from /proc/stat on Linux systems.
*/
fun getCpuUtilization(percent: Consumer<Array<Pair<String, Double>>>) {
if (isLinux()) {
CoroutineScope(Dispatchers.Default).launch {
val map1 = HashMap<String, cpustat>()
val map2 = HashMap<String, cpustat>()
val s1 = File("/proc/stat").readLines().filter { it.startsWith("cpu") }
s1.forEach {
val match = cpustat.regex.find(it)
if (match != null) {
val parts = match.groupValues
if (parts.size >= 11) {
val first = cpustat(
user = parts[2].toLong(),
nice = parts[3].toLong(),
system = parts[4].toLong(),
idle = parts[5].toLong(),
iowait = parts[6].toLong(),
irq = parts[7].toLong(),
softirq = parts[8].toLong(),
steal = parts[9].toLong(),
guest = parts[10].toLongOrNull() ?: 0L,
guestNice = parts.getOrNull(11)?.toLongOrNull() ?: 0L
)
map1[parts[1].trim()] = first
}
}
}
delay(1000) // Wait for 1 second to get a new snapshot
val s2 = File("/proc/stat").readLines().filter { it.startsWith("cpu") }
s2.forEach {
val match = cpustat.regex.find(it)
if (match != null) {
val parts = match.groupValues
if (parts.size >= 11) {
val second = cpustat(
user = parts[2].toLong(),
nice = parts[3].toLong(),
system = parts[4].toLong(),
idle = parts[5].toLong(),
iowait = parts[6].toLong(),
irq = parts[7].toLong(),
softirq = parts[8].toLong(),
steal = parts[9].toLong(),
guest = parts[10].toLongOrNull() ?: 0L,
guestNice = parts.getOrNull(11)?.toLongOrNull() ?: 0L
)
map2[parts[1].trim()] = second
}
}
}
// compare s1 and s2
val result = ArrayList<Pair<String, Double>>()
map1.forEach {
val key = it.key
val firstStat = it.value
val secondStat = map2[key]
if (secondStat != null) {
val usage = secondStat.cpuUsage(firstStat)
result.add(Pair(key, usage))
}
}
percent.accept(result.toTypedArray())
}
} else {
logger.error("Platform is not Linux, cannot get CPU utilization")
percent.accept(arrayOf(Pair("N/A", 0.0)))
}
}
/**
* Get network status from /proc/net/dev on Linux systems.
* @return List of netdev data class containing network device name, RX and TX statistics.
* Returns null if the platform is not Linux or if an error occurs while reading the file
*/
fun getNetworkStatus(): List<netdev>? {
return try {
if (isLinux()) {
val netdevFile = File("/proc/net/dev")
if (netdevFile.exists()) {
val lines = netdevFile.readLines().drop(2) // Skip the first two header lines
val regex = netdev.regex
val devices = lines.mapNotNull { line ->
val match = regex.find(line)
if (match != null) {
val parts = match.groupValues
val name = parts[1].trim().removeSuffix(":")
val rx = devstat(
Bytes = parts[2].toLong(),
Packets = parts[3].toLong(),
Errors = parts[4].toLong(),
Drops = parts[5].toLong(),
Fifo = parts[6].toLong(),
Frame = parts[7].toLong(),
Compressed = parts[8].toLong(),
Multicast = parts[9].toLong()
)
val tx = devstat(
Bytes = parts[10].toLong(),
Packets = parts[11].toLong(),
Errors = parts[12].toLong(),
Drops = parts[13].toLong(),
Fifo = parts[14].toLong(),
Frame = parts[15].toLong(),
Compressed = parts[16].toLong(),
Multicast = parts[17].toLong()
)
netdev(name, rx, tx)
} else {
null
}
}
devices
} else {
logger.error("/proc/net/dev does not exist")
null
}
} else {
logger.error("Platform is not Linux, cannot get network status")
null
}
} catch (e: Exception) {
logger.error("Error getting network status: ${e.message}")
null
}
}
/**
* Get network transfer speed for each network device.
* @param callback Consumer that accepts a map where keys are network device names and values are
* pairs of RX and TX bytes difference within a 1-second interval.
* This function runs in a coroutine and retrieves network statistics from /proc/net/dev on Linux
*/
fun getNetworkTransferSpeed(callback: Consumer<Map<String, Pair<Long, Long>>>) {
if (isLinux()) {
CoroutineScope(Dispatchers.Default).launch {
val netdevs = getNetworkStatus()
if (netdevs != null) {
val speedMap = netdevs.associate {
it.name to Pair(it.RX.Bytes, it.TX.Bytes)
}
delay(1000) // Wait for 1 second to get a new snapshot
val newNetdevs = getNetworkStatus()
if (newNetdevs != null) {
val newSpeedMap = newNetdevs.associate {
it.name to Pair(it.RX.Bytes, it.TX.Bytes)
}
val resultMap = mutableMapOf<String, Pair<Long, Long>>()
speedMap.forEach { (name, oldSpeed) ->
val newSpeed = newSpeedMap[name]
if (newSpeed != null) {
val rxDiff = newSpeed.first - oldSpeed.first
val txDiff = newSpeed.second - oldSpeed.second
resultMap[name] = Pair(rxDiff, txDiff)
}
}
callback.accept(resultMap)
} else callback.accept(emptyMap())
}
}
} else {
logger.error("Platform is not Linux, cannot get network transfer speed")
callback.accept(emptyMap())
}
}
/**
* Get CPU Thermal information from /sys/class/thermal/thermal_zone0/temp on Linux systems.
* @return CPU temperature in degrees Celsius. Returns 0.0 if the platform is not Linux or if an error occurs while reading the file.
*/
fun getCPUThermal(): Double {
return try{
if (isLinux()){
val p = Path("/sys/class/thermal/thermal_zone0/temp")
if (p.exists()) {
val vv = p.readText().trim().toDouble()
val tempCelsius = vv / 1000.0 // Convert from millidegrees Celsius to degrees Celsius
(tempCelsius*10).toInt() / 10.0 // Round to 1 decimal places
} else throw Exception("/sys/class/thermal/thermal_zone0/temp does not exist")
} else throw Exception("Platform is not Linux, cannot get CPU thermal info")
} catch (_ : Exception) {
0.0
}
}
}

3
src/sbc/cpuinfo.kt Normal file
View File

@@ -0,0 +1,3 @@
package sbc
data class cpuinfo(val core: Int, val hardware: String, val serialNumber: String)

49
src/sbc/cpustat.kt Normal file
View File

@@ -0,0 +1,49 @@
package sbc
/**
* Data class representing CPU statistics.
* @property user Time spent in user mode.
* @property nice Time spent in user mode with low priority (nice).
* @property system Time spent in system mode.
* @property idle Time spent in idle mode.
* @property iowait Time spent waiting for I/O operations to complete.
* @property irq Time spent servicing hardware interrupts.
* @property softirq Time spent servicing software interrupts.
* @property steal Time spent in involuntary wait by virtual CPUs.
* @property guest Time spent running a virtual CPU for guest operating systems.
* @property guestNice Time spent running a virtual CPU for guest operating systems with low priority (nice).
*/
@Suppress("unused")
class cpustat(val user: Long, val nice: Long, val system: Long, val idle: Long, val iowait: Long, val irq: Long, val softirq: Long, val steal: Long, val guest: Long, val guestNice: Long) {
companion object{
val regex = Regex("""cpu(\d?)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)""")
}
override fun toString(): String {
return "CPU Stat(user=$user, nice=$nice, system=$system, idle=$idle, iowait=$iowait, irq=$irq, softirq=$softirq)"
}
fun total(): Long {
return user + nice + system + idle + iowait + irq + softirq + steal + guest + guestNice
}
fun idle(): Long {
return idle + iowait
}
/**
* Calculate CPU usage percentage based on the current and previous cpustat instances.
* @param previous The previous cpustat instance to compare against.
* @return CPU usage percentage as a Double, rounded to one decimal place.
*/
fun cpuUsage(previous: cpustat): Double {
val totalDiff = total() - previous.total()
if (totalDiff < 0L) return 0.0 // Avoid division by zero
val idleDiff = idle() - previous.idle()
if (idleDiff < 0L) return 0.0 // Avoid negative idle difference
return (1.0*(totalDiff - idleDiff) / totalDiff * 100.0).let {
val x = (it * 10).toInt()
x/10.0
}
}
}

5
src/sbc/devstat.kt Normal file
View File

@@ -0,0 +1,5 @@
package sbc
data class devstat(val Bytes: Long, val Packets: Long, val Errors: Long, val Drops: Long, val Fifo: Long, val Frame: Long, val Compressed: Long, val Multicast: Long)

76
src/sbc/meminfo.kt Normal file
View File

@@ -0,0 +1,76 @@
package sbc
/**
* Memory Information data class from /proc/meminfo on Linux systems.
* all values are in kilobytes (KB).
* @property MemTotal Total usable RAM (i.e., physical RAM minus a few reserved bits and the kernel binary code).
* @property MemFree The amount of physical RAM, in kilobytes, left unused by the system.
* @property MemAvailable An estimate of how much memory is available for starting new applications, without swapping.
* @property Buffers The amount of memory, in kilobytes, used by kernel buffers.
* @property Cached The amount of memory, in kilobytes, used by the page cache and slabs.
* @property SwapTotal Total amount of swap space available, in kilobytes.
* @property SwapFree The amount of swap space that is currently unused, in kilobytes.
*/
@Suppress("unused")
class meminfo(val MemTotal: Long, val MemFree: Long, val MemAvailable: Long, val Buffers: Long, val Cached: Long, val SwapTotal: Long, val SwapFree: Long) {
companion object{
private const val MB_Threshold = 1024.0
private const val GB_Threshold = 1024.0 * MB_Threshold
/**
* Converts a memory value in kilobytes to a human-readable string.
* @param value The memory value in kilobytes.
* @return A string representation of the memory value in GB, MB, or KB, and round to two decimal places.
* For example, "1.50 GB", "512 MB", or "256 KB
*/
fun toText(value: Long) : String {
return when {
value >= 1024 * 1024 -> "${"%.2f".format(value / GB_Threshold)} GB"
value >= 1024 -> "${"%.2f".format(value / MB_Threshold)} MB"
else -> "$value KB"
}
}
}
override fun toString(): String {
return "MemTotal=${toText(MemTotal)}, MemFree=${toText(MemFree)}, MemAvailable=${toText(MemAvailable)}, Buffers=${toText(Buffers)}, Cached=${toText(Cached)}, SwapTotal=${toText(SwapTotal)}, SwapFree=${toText(SwapFree)}, "
}
/**
* Calculates the used memory in kilobytes.
* @return The amount of used memory in kilobytes.
*/
fun MemUsed(): Long {
return MemTotal - MemFree
}
/**
* Calculates the percentage of memory used.
* @return The percentage of memory used, as a double, rounded to two decimal places.
*/
fun percentUsed(): Double {
return if (MemTotal > 0) {
((1.0 * MemUsed() / MemTotal) * 100.0).let {
val x = (it * 100).toInt()
x / 100.0
}
} else {
0.0
}
}
/**
* Calculates the percentage of memory available.
* @return The percentage of memory available, as a double, rounded to two decimal places.
*/
fun percentFree(): Double {
return if (MemTotal > 0) {
((1.0 * MemFree / MemTotal) * 100.0).let {
val x = (it * 100).toInt()
x/ 100.0
}
} else {
0.0
}
}
}

28
src/sbc/netdev.kt Normal file
View File

@@ -0,0 +1,28 @@
package sbc
class netdev( val name: String,val RX: devstat, val TX: devstat) {
companion object{
val regex = Regex("""\s?(\S+):\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)""")
private const val KB_Size = 1024.0
private const val MB_Size = KB_Size * 1024.0
private const val GB_Size = MB_Size * 1024.0
/**
* Converts a long value to a human-readable text format.
* @param value The long value to convert.
* @return A string representation of the value, formatted with appropriate units.
* The value is formatted in bytes, kilobytes, megabytes, or gigabytes, and rounded to one decimal place.
*/
fun toText(value: Long): String {
return when {
value >= 1024 * 1024 * 1024 -> "${"%.1f".format(value / (GB_Size))} GB"
value >= 1024 * 1024 -> "${"%.1f".format(value / (MB_Size))} MB"
value >= 1024 -> "${"%.1f".format(value / KB_Size)} KB"
else -> "$value B"
}
}
}
}

View File

@@ -16,9 +16,13 @@ class Codes {
val gpioUnexportPath : Path = gpioPath.resolve("unexport")
private val logger = LoggerFactory.getLogger("Codes")
private val validAudioExtensions = setOf("wav", "mp3")
private const val KB_size = 1024 // 1 KB = 1024 bytes
private const val MB_size = 1024 * KB_size // 1 MB = 1024 KB
private const val GB_size = 1024 * MB_size // 1 GB = 1024 MB
private const val KB_size = 1024.0 // 1 KB = 1024 bytes
private const val MB_size = 1024.0 * KB_size // 1 MB = 1024 KB
private const val GB_size = 1024.0 * MB_size // 1 GB = 1024 MB
fun isLinux() : Boolean {
return Platform.isLinux()
}
fun haveGpioSupport() : Boolean {
if (Platform.isLinux()){
@@ -33,9 +37,9 @@ class Codes {
fun SizeToString(size: Long) : String {
return when {
size >= GB_size -> String.format("%.2f GB", size.toDouble() / GB_size)
size >= MB_size -> String.format("%.2f MB", size.toDouble() / MB_size)
size >= KB_size -> String.format("%.2f KB", size.toDouble() / KB_size)
size >= GB_size -> String.format("%.2f GB", size / GB_size)
size >= MB_size -> String.format("%.2f MB", size / MB_size)
size >= KB_size -> String.format("%.2f KB", size / KB_size)
else -> "$size bytes"
}
}