Compare commits

...

8 Commits

18 changed files with 1513 additions and 461 deletions

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{jquery.dataTables}" />
</component>
</project>

1
.idea/misc.xml generated
View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager"> <component name="MavenProjectsManager">

View File

@@ -10,6 +10,7 @@
<script src="public/js/jquery-3.7.1.min.js"></script> <script src="public/js/jquery-3.7.1.min.js"></script>
<script src="public/js/jquery.dataTables.min.js"></script> <script src="public/js/jquery.dataTables.min.js"></script>
<script src="public/js/all.min.js"></script> <script src="public/js/all.min.js"></script>
<script src="public/js/socket.io.min.js"></script>
<link rel="stylesheet" href="public/style/all.min.css"> <link rel="stylesheet" href="public/style/all.min.css">
<script src="index.js"></script> <script src="index.js"></script>
</head> </head>
@@ -29,11 +30,11 @@
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item pad-menu"> <li class="nav-item pad-menu">
<a class="nav-link" href="setting.html">Setting</a> <a class="nav-link nav-setting" href="setting.html">Setting</a>
</li> </li>
<li class="nav-item pad-menu"> <li class="nav-item pad-menu">
<form action="/logout" method="get"> <form action="/logout" method="get">
<button class="btn btn-outline-light" type="submit">Logout</button> <button class="btn btn-outline-light nav-logout" type="submit">Logout</button>
</form> </form>
</li> </li>
</ul> </ul>
@@ -54,25 +55,36 @@
<p id="streaming_status">No Status</p> <p id="streaming_status">No Status</p>
</div> </div>
<div class="col-12 col-sm-4 col-md-5 col-lg-5 col-xl-5"> <div class="col-12 col-sm-4 col-md-5 col-lg-5 col-xl-5">
<!-- <p id="system_information"> System Information </p>-->
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="fa-solid fa-microchip icon-system"></span> <span class="fa-solid fa-microchip icon-system"></span>
<p id="cpu_usage" class="padleft">100 %</p> <p id="cpu_usage" class="padleft">0 %</p>
</div> </div>
</div> </div>
<div class="col"> <div class="col">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="fa-solid fa-temperature-half icon-system"></span> <span class="fa-solid fa-temperature-half icon-system"></span>
<p id="cpu_temperature" class="padleft">55 °C</p> <p id="cpu_temperature" class="padleft">0 °C</p>
</div> </div>
</div> </div>
<div class="col"> <div class="col">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="fa-solid fa-memory icon-system"></span> <span class="fa-solid fa-memory icon-system"></span>
<p id="ram_usage" class="padleft">15 %</p> <p id="ram_usage" class="padleft">0 %</p>
</div>
</div>
<div class="col">
<div class="d-flex align-items-center">
<span class="fa-solid fa-upload icon-system"></span>
<p id="ethernet_TX" class="padleft">0 B/s</p>
</div>
</div>
<div class="col">
<div class="d-flex align-items-center">
<span class="fa-solid fa-download icon-system"></span>
<p id="ethernet_RX" class="padleft">0 B/s</p>
</div> </div>
</div> </div>
@@ -96,7 +108,7 @@
<br> <br>
<div class="row"> <div class="row">
<div class="col-3 "> <div class="col-3 ">
<p>Pan Speed</p> <p class="text-dash">Pan Speed</p>
<div class="btn-group-vertical"> <div class="btn-group-vertical">
<button id="pan1" class="btn btn-outline-dark btn-sm" value="16" onclick="set_pan_speed(16)">1</button> <button id="pan1" class="btn btn-outline-dark btn-sm" value="16" onclick="set_pan_speed(16)">1</button>
<button id="pan2" class="btn btn-outline-dark btn-sm" value="32" onclick="set_pan_speed(32)">2</button> <button id="pan2" class="btn btn-outline-dark btn-sm" value="32" onclick="set_pan_speed(32)">2</button>
@@ -109,7 +121,7 @@
<div class="row"> <div class="row">
<div class="col"></div> <div class="col"></div>
<div class="col"> <div class="col">
<a onmousedown="btn_up()" onmouseup="stop_movement()"> <a onmousedown="send_tilt_up()" onmouseup="send_stop_movement()">
<i class="btn-nav fa-solid fa-caret-up pad-top" title="Up"></i> <i class="btn-nav fa-solid fa-caret-up pad-top" title="Up"></i>
</a> </a>
</div> </div>
@@ -117,7 +129,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<a onmousedown="btn_left()" onmouseup="stop_movement()"> <a onmousedown="send_pan_left()" onmouseup="send_stop_movement()">
<i class="btn-nav fa-solid fa-caret-left pad-left" title="Left"></i> <i class="btn-nav fa-solid fa-caret-left pad-left" title="Left"></i>
</a> </a>
</div> </div>
@@ -127,7 +139,7 @@
</a> </a>
</div> </div>
<div class="col"> <div class="col">
<a onmousedown="btn_right()" onmouseup="stop_movement()"> <a onmousedown="send_pan_right()" onmouseup="send_stop_movement()">
<i class="btn-nav fa-solid fa-caret-right pad-right" title="Right"></i> <i class="btn-nav fa-solid fa-caret-right pad-right" title="Right"></i>
</a> </a>
</div> </div>
@@ -135,7 +147,7 @@
<div class="row"> <div class="row">
<div class="col"></div> <div class="col"></div>
<div class="col"> <div class="col">
<a onmousedown="btn_down()" onmouseup="stop_movement()"> <a onmousedown="send_tilt_down()" onmouseup="send_stop_movement()">
<i class="i btn-nav fa-solid fa-caret-down pad-bottom" title="Down"></i> <i class="i btn-nav fa-solid fa-caret-down pad-bottom" title="Down"></i>
</a> </a>
</div> </div>
@@ -144,7 +156,7 @@
</div> </div>
</div> </div>
<div class="col-3"> <div class="col-3">
<p>Tilt Speed</p> <p class="text-dash">Tilt Speed</p>
<div class="btn-group-vertical"> <div class="btn-group-vertical">
<button id="tilt1" class="btn btn-outline-dark btn-sm" value="16" onclick="set_tilt_speed(16)">1</button> <button id="tilt1" class="btn btn-outline-dark btn-sm" value="16" onclick="set_tilt_speed(16)">1</button>
<button id="tilt2" class="btn btn-outline-dark btn-sm" value="32" onclick="set_tilt_speed(32)">2</button> <button id="tilt2" class="btn btn-outline-dark btn-sm" value="32" onclick="set_tilt_speed(32)">2</button>
@@ -154,14 +166,14 @@
</div> </div>
</div> </div>
<div> <div>
<p class="text-center">Zoom</p> <p class="text-center text-dash">Zoom</p>
<div class="row"> <div class="row">
<div class="col-10"> <div class="col-10">
<input id="zoom" type="range" class="form-range" min="1" max="10909" oninput="this.nextElementSibling.value = this.value"> <input id="zoom" type="range" class="form-range" min="1" max="10909" oninput="this.nextElementSibling.value = this.value">
<output class="output"></output> <output class="output"></output>
</div> </div>
<div class="col-2"> <div class="col-2">
<button class="btn btn-primary" onclick="set_zoom()">SET</button> <button class="btn btn-primary" id="set_zoom" onclick="set_zoom()">SET</button>
</div> </div>
</div> </div>
</div> </div>
@@ -181,7 +193,7 @@
</a> </a>
</div> </div>
<br> <br>
<p> Content 1</p> <p id="desc_content1"> Content 1</p>
</div> </div>
<div class="col"> <div class="col">
@@ -194,7 +206,7 @@
</a> </a>
</div> </div>
<br> <br>
<p> Content 2</p> <p id="desc_content2"> Content 2</p>
</div> </div>
<div class="col"> <div class="col">
@@ -207,7 +219,7 @@
</a> </a>
</div> </div>
<br> <br>
<p> Content 3</p> <p id="desc_content3"> Content 3</p>
</div> </div>
<div class="col"> <div class="col">
@@ -220,7 +232,7 @@
</a> </a>
</div> </div>
<br> <br>
<p> Content 4</p> <p id="desc_content4"> Content 4</p>
</div> </div>
<div class="col"> <div class="col">
@@ -233,12 +245,12 @@
</a> </a>
</div> </div>
<br> <br>
<p>Content 5</p> <p id="desc_content5">Content 5</p>
</div> </div>
</div> </div>
<br> <br>
<div> <div>
<label for="customRange" class="form-label">Volume</label> <label for="customRange" class="form-label text-dash">Volume</label>
<div class="row"> <div class="row">
<div class="col-10 pad-bar"> <div class="col-10 pad-bar">
@@ -247,10 +259,10 @@
</div> </div>
<div class="col-2"> <div class="col-2">
<div> <div>
<button id="mute" class="btn-mute show" onclick="mute()" title="mute"> <button id="mute" class="btn-mute show" onclick="send_mute()" title="mute">
<i class="fa-solid fa-volume-high"></i> <i class="fa-solid fa-volume-high"></i>
</button> </button>
<button id="unmute" class="btn-mute hide" onclick="unmute()" title="unmute"> <button id="unmute" class="btn-mute hide" onclick="send_unmute()" title="unmute">
<i class="fa-solid fa-volume-xmark"></i> <i class="fa-solid fa-volume-xmark"></i>
</button> </button>
</div> </div>
@@ -264,7 +276,7 @@
<!-- SECTION 3 --> <!-- SECTION 3 -->
<div class="container-fluid footer1 outside"> <div class="container-fluid footer1 outside">
<p>2024 Copyright &copy; Galva Technologies. All rights reserved.</p> <p class="footer">2024 Copyright &copy; Galva Technologies. All rights reserved.</p>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -12,78 +12,90 @@ let ws;
let camerastream; let camerastream;
// socketio
let socketio;
let files = [null,null,null,null,null]; let files = [null,null,null,null,null];
let getbase64handle; let getbase64handle;
let getsysteminfohandle; let getsysteminfohandle;
let $zoom;
let $mute;
let $unmute;
window.addEventListener("unload",function (){ window.addEventListener("unload",function (){
clearInterval(getbase64handle); clearInterval(getbase64handle);
clearInterval(getsysteminfohandle); clearInterval(getsysteminfohandle);
if (ws.readyState === WebSocket.OPEN){ send_stop_audio();
ws.send(JSON.stringify({
command: "STOP AUDIO",
data: 0
}));
// wait a while // wait a while
const start = Date.now(); const start = Date.now();
while(Date.now()-start<200){} while(Date.now()-start<200){}
}
ws.close(); ws.close();
}) })
window.addEventListener("load", function (){ window.addEventListener("load", function (){
$zoom = $('#zoom');
$mute = $('#mute');
$unmute = $('#unmute');
camerastream = document.getElementById("camerastream"); camerastream = document.getElementById("camerastream");
// pilihan komunikasi, disable salah satu
initialize_socketio();
//initialize_websocket();
// Update camera stream every 50ms / 20fps // Update camera stream every 50ms / 20fps
getbase64handle = setInterval(function (){ getbase64handle = setInterval(function (){
if (ws.readyState === WebSocket.OPEN){ send_get_base64();
ws.send(JSON.stringify({
command: "GET BASE64",
data: video_quality
}));
} else update_camerastream(null);
},1000/15); },1000/15);
getsysteminfohandle = setInterval(function (){ getsysteminfohandle = setInterval(function (){
if (ws.readyState === WebSocket.OPEN){ send_get_system_info();
ws.send(JSON.stringify({
command: "GET SYSTEM INFO",
data: 0
}));
}
}, 5000); }, 5000);
set_pan_speed(32);
set_tilt_speed(32);
})
function initialize_socketio(){
socketio = io(":3001/socketio");
if (socketio){
socketio.on("connect",()=>{
console.log("Socket.io Connected to the server");
send_get_max_zoom();
send_get_volume();
send_get_zoom();
send_get_resolution();
send_get_audiofiles();
})
socketio.on("disconnect",(reason)=>{
console.log("Socket.io Disconnected from server because "+reason);
})
socketio.on("connect_error",(error)=>{
console.log("Socket.io Connection error "+error);
});
socketio.on("message",(data)=>{
let dx = JSON.parse(data);
process_command(dx);
});
}
}
function initialize_websocket(){
ws = new WebSocket("/ws"); ws = new WebSocket("/ws");
if (ws){
ws.onopen = function(){ ws.onopen = function(){
console.log("Connected to the server"); console.log("Connected to the server");
// request basic parameters // request basic parameters
ws.send(JSON.stringify({ send_get_max_zoom();
command: "GET MAX ZOOM", send_get_volume();
data: 0 send_get_zoom();
})); send_get_resolution();
ws.send(JSON.stringify({ send_get_audiofiles();
command: "GET VOLUME",
data: 0
}));
ws.send(JSON.stringify({
command: "GET ZOOM",
data: 0
}));
ws.send(JSON.stringify({
command: "GET RESOLUTION",
data: 0
}));
ws.send(JSON.stringify({
command: "GET AUDIOFILES",
data: 0
}))
} }
ws.onmessage = function(event){ ws.onmessage = function(event){
@@ -92,121 +104,7 @@ window.addEventListener("load", function (){
* @type {{reply: string, data: string|number, additional: string}} dx * @type {{reply: string, data: string|number, additional: string}} dx
*/ */
let dx = JSON.parse(event.data); let dx = JSON.parse(event.data);
switch (dx.reply){ process_command(dx);
case "GET BASE64":
if (dx.data.startsWith("data:image/jpeg;base64,")){
update_camerastream(dx.data);
} else update_camerastream(null);
if (dx.additional && dx.additional.length>0){
let stat = JSON.parse(dx.additional);
if (Array.isArray(stat) && stat.length === 3){
$('#streaming_status').html("Streaming at "+stat[0]+"x"+stat[1]+" "+stat[2]+"fps");
}
}
break;
case "SET VOLUME":
console.log("Set Volume: "+dx.data);
break;
case "GET VOLUME":
console.log("Get Volume: "+dx.data);
$('#customRange').val(dx.data);
break;
case "GET MAX ZOOM":
console.log("Get Max Zoom: "+dx.data);
let zoom = document.getElementById("zoom");
zoom.min = 1;
zoom.max = dx.data;
break;
case "GET ZOOM":
document.getElementById("zoom").value = dx.data;
console.log("Get Zoom: "+dx.data);
break;
case "GET RESOLUTION":
console.log("Get Resolution: "+dx.data);
break;
case "PAN LEFT":
console.log("Pan Left");
break;
case "PAN RIGHT":
console.log("Pan Right");
break;
case "TILT UP":
console.log("Tilt Up");
break;
case "TILT DOWN":
console.log("Tilt Down");
break;
case "STOP MOVEMENT":
console.log("Stop Movement");
break;
case "SET ZOOM":
console.log("Set Zoom: "+dx.data);
break;
case "PLAY AUDIO":
console.log("Play Audio: "+dx.data);
if (dx.data.startsWith("Failed")){
alert(dx.data);
} else $('#status_player').html("Playing Audio "+dx.data);
break;
case "STOP AUDIO":
console.log("Stop Audio");
document.getElementById("status_player").innerHTML = "Stop Playback";
break;
case 'SET VIDEO QUALITY':
console.log("Set Video Quality: "+dx.data);
break;
case "GET SYSTEM INFO":
//console.log("Get System Info: "+dx.data);
/**
* @type {{cpu_temperature: string, ram_usage: string, cpu: string, cpu0: string, cpu1: string, cpu2: string, cpu3: string}} systeminfo
*/
let systeminfo = JSON.parse(dx.data);
if (systeminfo.cpu_temperature && systeminfo.cpu_temperature.length>0) $('#cpu_temperature').html(`${systeminfo.cpu_temperature} °C`);
if (systeminfo.cpu && systeminfo.cpu.length>0) $('#cpu_usage').html(`${systeminfo.cpu} %`);
if (systeminfo.ram_usage && systeminfo.ram_usage.length>0) $('#ram_usage').html(`${systeminfo.ram_usage} %`);
break;
case "GET AUDIOFILES":
console.log("Get Audio Files: "+dx.data);
let audiofiles = JSON.parse(dx.data);
if (audiofiles.preset1 && audiofiles.preset1.length>0 && audiofiles.preset1!== "null") {
files[0] = audiofiles.preset1;
play_on(1);
} else {
files[0] = null;
play_off(1);
}
if (audiofiles.preset2 && audiofiles.preset2.length>0 && audiofiles.preset2!== "null") {
files[1] = audiofiles.preset2;
play_on(2);
} else {
files[1] = null;
play_off(2);
}
if (audiofiles.preset3 && audiofiles.preset3.length>0 && audiofiles.preset3!== "null") {
files[2] = audiofiles.preset3;
play_on(3);
} else {
files[2] = null;
play_off(3);
}
if (audiofiles.preset4 && audiofiles.preset4.length>0 && audiofiles.preset4!== "null") {
files[3] = audiofiles.preset4;
play_on(4);
} else {
files[3] = null;
play_off(4);
}
if (audiofiles.preset5 && audiofiles.preset5.length>0 && audiofiles.preset5!== "null") {
files[4] = audiofiles.preset5;
play_on(5);
} else {
files[4] = null;
play_off(5);
}
break;
}
} }
ws.onclose = function(){ ws.onclose = function(){
console.log("Connection closed"); console.log("Connection closed");
@@ -214,10 +112,9 @@ window.addEventListener("load", function (){
ws.onerror = function(){ ws.onerror = function(){
console.log("Error in connection"); console.log("Error in connection");
} }
}
set_pan_speed(32); }
set_tilt_speed(32);
})
@@ -237,37 +134,62 @@ function update_camerastream(value){
} }
/**
* Disable play button
* @param index 1 - 5
*/
function play_off(index){ function play_off(index){
$('#div'+index).prop("className", "button-container div-disabled"); $('#div'+index).prop("className", "button-container div-disabled");
$('#btn_play'+index).prop("className", "btn-play1 show text-light"); $('#btn_play'+index)
.prop("className", "btn-play1 show text-light");
} }
/**
* Enable play button
* @param index 1 - 5
*/
function play_on(index){ function play_on(index){
let content = $('#desc_content'+index).html();
$('#div'+index).prop("className", "button-container"); $('#div'+index).prop("className", "button-container");
$('#btn_play'+index).prop("className", "btn-play1 show"); $('#btn_play'+index)
.prop("className", "btn-play1 show")
.prop("title","Play "+content);
$('#btn_stop'+index)
.prop("title","Stop "+content);
} }
/**
* Show play button and hide stop button
* @param index 1 - 5
*/
function show_play_and_hide_stop(index){ function show_play_and_hide_stop(index){
$('#btn_play'+index).prop("className", "btn-play1 show"); $('#btn_play'+index).prop("className", "btn-play1 show");
$('#btn_stop'+index).prop("className", "btn-play1 hide"); $('#btn_stop'+index).prop("className", "btn-play1 hide");
} }
/**
* Show stop button and hide play button
* @param index 1 - 5
*/
function show_stop_and_hide_play(index){ function show_stop_and_hide_play(index){
$('#btn_play'+index).prop("className", "btn-play1 hide"); $('#btn_play'+index).prop("className", "btn-play1 hide");
$('#btn_stop'+index).prop("className", "btn-play1 show"); $('#btn_stop'+index).prop("className", "btn-play1 show");
} }
/**
* Enable all play buttons
*/
function allplay(){ function allplay(){
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
if (files[i]) play_on(i+1); if (files[i]) play_on(i+1);
} }
} }
/**
* When play button is clicked
* @param index 1 - 5
*/
function play_button(index){ function play_button(index){
show_stop_and_hide_play(index); show_stop_and_hide_play(index);
$('#status_player').html("Play"); $('#status_player').html("Play");
@@ -277,26 +199,159 @@ function play_button(index){
send_play_audio(index); send_play_audio(index);
} }
/**
* When stop button is clicked
* @param index 1 - 5
*/
function stop_button(index){ function stop_button(index){
show_play_and_hide_stop(index); show_play_and_hide_stop(index);
$('#status_player').html("Stop"); $('#status_player').html("Stop");
allplay(); allplay();
send_stop_audio(); send_stop_audio();
} }
function send_get_audiofiles(){
let cmd = JSON.stringify({
command: "GET AUDIOFILES",
data: 0
});
if (ws){
if (ws.readyState===WebSocket.OPEN){
ws.send(cmd)
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
}
function send_get_resolution(){
let cmd = JSON.stringify({
command: "GET RESOLUTION",
data: 0
});
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
}
function send_get_zoom(){
let cmd = JSON.stringify({
command: "GET ZOOM",
data: 0
});
if (ws){
if (ws.readyState===WebSocket.OPEN){
ws.send(cmd);
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
}
function send_get_volume(){
let cmd = JSON.stringify({
command: "GET VOLUME",
data: 0
});
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
}
function send_get_max_zoom(){
let cmd = JSON.stringify({
command: "GET MAX ZOOM",
data: 0
});
if (ws){
if (ws && ws.readyState===WebSocket.OPEN){
ws.send(cmd);
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
}
function send_get_system_info(){
let cmd = JSON.stringify({
command: "GET SYSTEM INFO",
data: 0
});
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
}
/** /**
* Send command to stop audio * Send command to stop audio
*/ */
function send_stop_audio(){ function send_stop_audio(){
if (ws.readyState === WebSocket.OPEN){ let cmd = JSON.stringify({
ws.send(JSON.stringify({
command: "STOP AUDIO", command: "STOP AUDIO",
data: 0 data: 0
})); });
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
} else console.log("WebSocket is not open"); } else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
}
function send_get_base64(){
let cmd = JSON.stringify({
command: "GET BASE64",
data: video_quality
});
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
return;
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
return;
} else console.log("Socket.io is not connected");
}
update_camerastream(null);
} }
/** /**
@@ -304,119 +359,198 @@ function send_stop_audio(){
* @param {number} index 1 - 5 * @param {number} index 1 - 5
*/ */
function send_play_audio(index){ function send_play_audio(index){
if (ws.readyState === WebSocket.OPEN){ let cmd = JSON.stringify({
ws.send(JSON.stringify({
command: "PLAY AUDIO", command: "PLAY AUDIO",
data: index data: index
})); });
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
} else console.log("WebSocket is not open"); } else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
} }
function unmute(){ function send_unmute(){
$('#mute').prop("className", "btn-mute show"); let cmd = JSON.stringify({
$('#unmute').prop("className", "btn-mute hide");
console.log("unmute")
if (ws.readyState === WebSocket.OPEN){
ws.send(JSON.stringify({
command: "UNMUTE", command: "UNMUTE",
data: 0 data: 0
})); });
} else console.log("WebSocket is not open"); if (ws){
}
function mute(){
$('#mute').prop("className", "btn-mute hide");
$('#unmute').prop("className", "btn-mute show");
console.log("mute")
if (ws.readyState === WebSocket.OPEN){ if (ws.readyState === WebSocket.OPEN){
ws.send(JSON.stringify({ ws.send(cmd);
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
}
function send_mute(){
let cmd = JSON.stringify({
command: "MUTE", command: "MUTE",
data: 0 data: 0
})); });
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
} else console.log("WebSocket is not open"); } else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
}
function send_tilt_up(){
let cmd = JSON.stringify({
command: "TILT UP",
data: tilt_speed
});
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
btn_center_off()
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
btn_center_off()
} else console.log("Socket.io is not connected");
}
}
function send_tilt_down(){
let cmd = JSON.stringify({
command: "TILT DOWN",
data: tilt_speed
});
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
btn_center_off()
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
btn_center_off()
} else console.log("Socket.io is not connected");
}
}
function send_pan_left(){
let cmd = JSON.stringify({
command: "PAN LEFT",
data: pan_speed
});
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
btn_center_off();
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
btn_center_off();
} else console.log("Socket.io is not connected");
}
}
function send_pan_right(){
let cmd = JSON.stringify({
command: "PAN RIGHT",
data: pan_speed
});
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
btn_center_off()
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
btn_center_off()
} else console.log("Socket.io is not connected");
}
}
function send_stop_movement(){
let cmd = JSON.stringify({
command: "STOP MOVEMENT",
data: 0
});
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
}
function send_set_zoom(value){
let cmd = JSON.stringify({
command: "SET ZOOM",
data: value
});
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
}
function send_set_volume(value){
let cmd = JSON.stringify({
command: "SET VOLUME",
data: value
});
if (ws){
if (ws.readyState === WebSocket.OPEN){
ws.send(cmd);
} else console.log("WebSocket is not open");
} else if (socketio){
if (socketio.connected){
socketio.emit("message",cmd);
} else console.log("Socket.io is not connected");
}
} }
function btn_center_off(){ function btn_center_off(){
$('#btn_center').prop("className", "btn-center-unactive fa-solid fa-circle"); $('#btn_center').prop("className", "btn-center-unactive fa-solid fa-circle");
} }
function btn_up(){
console.log("btn_up")
if (ws.readyState === WebSocket.OPEN){
ws.send(JSON.stringify({
command: "TILT UP",
data: tilt_speed
}));
btn_center_off()
} else console.log("WebSocket is not open");
}
function btn_left(){
console.log("btn_left")
if (ws.readyState === WebSocket.OPEN){
ws.send(JSON.stringify({
command: "PAN LEFT",
data: pan_speed
}));
btn_center_off();
} else console.log("WebSocket is not open");
}
function btn_right(){
console.log("btn_right")
if (ws.readyState === WebSocket.OPEN){
ws.send(JSON.stringify({
command: "PAN RIGHT",
data: pan_speed
}));
btn_center_off()
} else console.log("WebSocket is not open");
}
function btn_down(){
console.log("btn_down")
if (ws.readyState === WebSocket.OPEN){
ws.send(JSON.stringify({
command: "TILT DOWN",
data: tilt_speed
}));
btn_center_off()
} else console.log("WebSocket is not open");
}
function stop_movement(){
console.log("stop_movement")
if (ws.readyState === WebSocket.OPEN){
ws.send(JSON.stringify({
command: "STOP MOVEMENT",
data: 0
}));
} else console.log("WebSocket is not open");
}
function set_zoom(){ function set_zoom(){
let zoom = document.getElementById("zoom"); send_set_zoom($('#zoom').val());
console.log("set_zoom "+zoom.value)
if (ws.readyState === WebSocket.OPEN){
ws.send(JSON.stringify({
command: "SET ZOOM",
data: zoom.value
}));
} else console.log("WebSocket is not open");
} }
function set_volumeoutput(value){ function set_volumeoutput(value){
$('#volumebarvalue').val(value); $('#volumebarvalue').val(value);
send_set_volume(value);
console.log("set_volumeoutput "+value)
if (ws.readyState === WebSocket.OPEN){
ws.send(JSON.stringify({
command: "SET VOLUME",
data: value
}));
} else console.log("WebSocket is not open");
} }
function set_pan_speed(value){ function set_pan_speed(value){
pan_speed = value; pan_speed = value;
console.log("set_pan_speed "+value);
clearpan(); clearpan();
let classvalue = "btn btn-dark btn-sm"; let classvalue = "btn btn-dark btn-sm";
switch(pan_speed){ switch(pan_speed){
@@ -438,7 +572,6 @@ function set_pan_speed(value){
function set_tilt_speed(value){ function set_tilt_speed(value){
tilt_speed = value; tilt_speed = value;
console.log("set_tilt_speed "+value);
cleartilt(); cleartilt();
let classvalue = "btn btn-dark btn-sm"; let classvalue = "btn btn-dark btn-sm";
switch (tilt_speed){ switch (tilt_speed){
@@ -477,5 +610,148 @@ function change_video_quality(){
video_quality = btn.checked?"HQ":"LQ"; video_quality = btn.checked?"HQ":"LQ";
} }
/**
* Process Command
* @param {{reply: string, data: string, additional: string}}dx
*/
function process_command(dx){
switch (dx.reply){
case "GET BASE64":
if (dx.data.startsWith("data:image/jpeg;base64,")){
update_camerastream(dx.data);
} else update_camerastream(null);
if (dx.additional && dx.additional.length>0){
let stat = JSON.parse(dx.additional);
if (Array.isArray(stat) && stat.length === 3){
$('#streaming_status').html("Streaming at "+stat[0]+"x"+stat[1]+" "+stat[2]+"fps");
}
}
break;
case "SET VOLUME":
//console.log("Set Volume: "+dx.data);
break;
case "GET VOLUME":
//console.log("Get Volume: "+dx.data);
$('#customRange').val(dx.data);
break;
case "GET MAX ZOOM":
$zoom
.prop("min", 1)
.prop("max", dx.data);
break;
case "GET ZOOM":
$zoom.val(dx.data);
break;
case "GET RESOLUTION":
//console.log("Get Resolution: "+dx.data);
break;
case "PAN LEFT":
//console.log("Pan Left");
break;
case "PAN RIGHT":
//console.log("Pan Right");
break;
case "TILT UP":
//console.log("Tilt Up");
break;
case "TILT DOWN":
//console.log("Tilt Down");
break;
case "STOP MOVEMENT":
//console.log("Stop Movement");
break;
case "SET ZOOM":
//console.log("Set Zoom: "+dx.data);
break;
case "PLAY AUDIO":
//console.log("Play Audio: "+dx.data);
if (dx.data.startsWith("Failed")){
alert(dx.data);
} else $('#status_player').html("Playing Audio "+dx.data);
break;
case "STOP AUDIO":
//console.log("Stop Audio");
document.getElementById("status_player").innerHTML = "Stop Playback";
break;
case 'SET VIDEO QUALITY':
//console.log("Set Video Quality: "+dx.data);
break;
case "MUTE":
//console.log("Mute");
$mute.prop("className", "btn-mute hide");
$unmute.prop("className", "btn-mute show");
break;
case "UNMUTE":
//console.log("Unmute");
$mute.prop("className", "btn-mute show");
$unmute.prop("className", "btn-mute hide");
break;
case "GET SYSTEM INFO":
//console.log("Get System Info: "+dx.data);
/**
* @type {{cpu_temperature: string, ram_usage: string, cpu: string, cpu0: string, cpu1: string, cpu2: string, cpu3: string}} systeminfo
*/
let systeminfo = JSON.parse(dx.data);
if (systeminfo.cpu_temperature && systeminfo.cpu_temperature.length>0) $('#cpu_temperature').html(`${systeminfo.cpu_temperature} °C`);
if (systeminfo.cpu && systeminfo.cpu.length>0) $('#cpu_usage').html(`${systeminfo.cpu} %`);
if (systeminfo.ram_usage && systeminfo.ram_usage.length>0) $('#ram_usage').html(`${systeminfo.ram_usage} %`);
if (systeminfo.end0_TX && systeminfo.end0_TX.length>0) $('#ethernet_TX').html(systeminfo.end0_TX);
if (systeminfo.end0_RX && systeminfo.end0_RX.length>0) $('#ethernet_RX').html(systeminfo.end0_RX);
break;
case "GET AUDIOFILES":
//console.log("Get Audio Files: "+dx.data);
let audiofiles = JSON.parse(dx.data);
if (audiofiles.preset1 && audiofiles.preset1.length>0 && audiofiles.preset1!== "null") {
files[0] = audiofiles.preset1;
$('#desc_content1').html(audiofiles.preset1);
play_on(1);
} else {
files[0] = null;
$('#desc_content1').html("");
play_off(1);
}
if (audiofiles.preset2 && audiofiles.preset2.length>0 && audiofiles.preset2!== "null") {
files[1] = audiofiles.preset2;
$('#desc_content2').html(audiofiles.preset2);
play_on(2);
} else {
files[1] = null;
$('#desc_content2').html("");
play_off(2);
}
if (audiofiles.preset3 && audiofiles.preset3.length>0 && audiofiles.preset3!== "null") {
files[2] = audiofiles.preset3;
$('#desc_content3').html(audiofiles.preset3);
play_on(3);
} else {
files[2] = null;
$('#desc_content3').html("");
play_off(3);
}
if (audiofiles.preset4 && audiofiles.preset4.length>0 && audiofiles.preset4!== "null") {
files[3] = audiofiles.preset4;
$('#desc_content4').html(audiofiles.preset4);
play_on(4);
} else {
files[3] = null;
$('#desc_content4').html("");
play_off(4);
}
if (audiofiles.preset5 && audiofiles.preset5.length>0 && audiofiles.preset5!== "null") {
files[4] = audiofiles.preset5;
$('#desc_content5').html(audiofiles.preset5);
play_on(5);
} else {
files[4] = null;
$('#desc_content5').html("");
play_off(5);
}
break;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

7
Html/html/public/js/socket.io.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -833,7 +833,6 @@ input.checkbox{
justify-content: center; justify-content: center;
gap: 10px; gap: 10px;
} }
/* Style the labels (Off and On) */ /* Style the labels (Off and On) */
.toggle-label { .toggle-label {
@@ -911,3 +910,433 @@ select {
.padleft{ .padleft{
padding-left: 3px; padding-left: 3px;
} }
.nav-setting{
font-size: 1.2em;
}
.nav-logout{
font-size: 1.2em !important;
}
/*-------------------------------------SET RESPONSIVE -----------------*/
@media (max-width: 900px) {
/*Pan Tilt Speed*/
button#tilt1,
button#tilt2,
button#tilt3,
button#tilt4,
button#pan1,
button#pan2,
button#pan3,
button#pan4{
padding: 0.75rem 1rem;
font-size: 1.2rem;
border-radius: .2rem;
}
/*End Pan Tilt Speed*/
/* ---- NAVIGATION ----- */
.btn-nav{
margin: 0;
font-size: 7em;
color: lightgray;
}
.btn-nav:hover{
color: white;
}
.btn-nav:active{
color: dodgerblue;
}
.btn-nav-center{
font-size: 3em;
top: -12px;
color: dodgerblue;
}
.btn-center-unactive{
font-size: 2em;
color: #434343;
}
.pad-img{
padding: 0;
}
.img-cctv{
box-shadow: rgba(14, 30, 37, 0.12) 0px 2px 4px 0px, rgba(14, 30, 37, 0.32) 0px 2px 16px 0px;
border-radius: 10px;
margin: 1.5em;
}
.btn-circle{
width: 13em;
height: 13em;
background-color: black;
align-items: center;
align-content: center;
margin: 5%;
border-radius: 50%;
box-shadow: rgba(0, 0, 0, 0.2) 0px 12px 28px 0px, rgba(0, 0, 0, 0.1) 0px 2px 4px 0px, rgba(255, 255, 255, 0.05) 0px 0px 0px 1px inset;
}
.pad-top{
margin-top: -17px;
margin-bottom: -10px;
}
.pad-left{
margin-top: -40px;
margin-right: -20px;
}
.pad-right{
margin-top: -40px;
margin-left: -20px;
}
.pad-bottom{
margin-top: -40px;
}
/* ---- END - NAVIGATION ----- */
/* Mute Button */
.btn-mute{
text-decoration: none;
background-color: #f7f7f7;
background-image: -webkit-gradient(linear, left top, left bottom, from(#f7f7f7), to(#e7e7e7));
background-image: -webkit-linear-gradient(top, #f7f7f7, #e7e7e7);
background-image: -moz-linear-gradient(top, #f7f7f7, #e7e7e7);
background-image: -ms-linear-gradient(top, #f7f7f7, #e7e7e7);
background-image: -o-linear-gradient(top, #f7f7f7, #e7e7e7);
color: black;
width: 2.5em;
height: 2.5em;
padding: 0.2em;
position: relative;
text-align: center;
border-radius: 50%;
box-shadow: 0px 3px 8px #aaa, inset 0px 2px 3px #fff;
border: solid 1px transparent;
font-size: 1.1em;
}
/* End Mute Button */
.toggle-label {
font-size: 16px;
color: #555;
margin-bottom: 15px;
margin-right: 5px;
margin-left: 5px;
text-align: right;
}
/* Style the toggle switch (input) */
.toggle-switch {
-webkit-appearance: none;
appearance: none;
width: 55px;
height: 30px;
background-color: #ccc;
border-radius: 25px;
position: relative;
transition: background-color 0.3s;
cursor: pointer;
}
/* Circle inside the switch */
.toggle-switch::before {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 24px;
height: 24px;
background-color: white;
border-radius: 50%;
transition: transform 0.3s;
}
/* When checked (toggle to On), change background color and move the circle */
.toggle-switch:checked {
background-color: #0d6efd;
}
.toggle-switch:checked::before {
transform: translateX(25px);
}
.align-right{
text-align: right;
}
/* End Switch */
}
@media (min-width: 900px) and (max-width: 1050px) {
#streaming_status{
text-align: left;
font-size: 1.8rem;
}
.padleft{
padding-left: 3px;
font-size: 1.8rem;
}
.icon-system{
margin-top: -13px;
font-size: 1.8rem;
}
button#tilt1,
button#tilt2,
button#tilt3,
button#tilt4,
button#pan1,
button#pan2,
button#pan3,
button#pan4{
padding: 0.75rem 1.5rem;
font-size: 2.5rem;
border-radius: .2rem;
}
.text-dash{
font-size: 1.8rem;
}
.text-status1{
color: black;
font-size: 3em;
}
/* ----- SWITCH ------ */
.toggle-label {
font-size: 1.8rem;
color: #555;
margin-bottom: 15px;
margin-right: 5px;
margin-left: 5px;
text-align: right;
}
/* Style the toggle switch (input) */
.toggle-switch {
-webkit-appearance: none;
appearance: none;
/*width: 50px;*/
/*height: 25px;*/
width: 75px;
height: 50px;
background-color: #ccc;
border-radius: 25px;
position: relative;
transition: background-color 0.3s;
cursor: pointer;
}
/* Circle inside the switch */
.toggle-switch::before {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 44px;
height: 44px;
background-color: white;
border-radius: 50%;
transition: transform 0.3s;
}
/* When checked (toggle to On), change background color and move the circle */
.toggle-switch:checked {
background-color: #0d6efd;
}
.toggle-switch:checked::before {
transform: translateX(25px);
}
/*----- END SWITCH -----*/
/* --- Button CONTENT ---- */
.btn-play1{
text-decoration: none;
background-color: #f7f7f7;
background-image: -webkit-gradient(linear, left top, left bottom, from(#f7f7f7), to(#e7e7e7));
background-image: -webkit-linear-gradient(top, #f7f7f7, #e7e7e7);
background-image: -moz-linear-gradient(top, #f7f7f7, #e7e7e7);
background-image: -ms-linear-gradient(top, #f7f7f7, #e7e7e7);
background-image: -o-linear-gradient(top, #f7f7f7, #e7e7e7);
color: black;
/*width: 4.2em;*/
/*height: 4.2em;*/
width: 7em;
height: 7em;
position: relative;
text-align: center;
border-radius: 50%;
box-shadow: 0px 3px 8px #aaa, inset 0px 2px 3px #fff;
border: solid 1px transparent;
}
.btn-play1:before {
content: "";
display: block;
background: #fff;
border-top: 2px solid #ddd;
position: absolute;
z-index: -1;
border-radius: 50%;
box-shadow: inset 0px 8px 48px #ddd;
}
.btn-play1:active {
box-shadow: 0px 3px 8px #aaa inset, 0px 2px 3px #fff;
}
.btn-play1:hover {
text-decoration: none;
/*color: #555;*/
color: black;
background: #f5f5f5;
}
.icon1{
margin: 27%;
font-size: 4.5em;
position: absolute;
top: -9px;
left: -1px;
bottom: 0;
right: 0;
text-align: center;
}
.icon2{
margin: 27%;
font-size: 4.5em;
position: absolute;
top: -9px;
left: 5px;
bottom: 0;
right: 0;
text-align: center;
}
/* --- END Button CONTENT ---- */
.btn-mute{
text-decoration: none;
background-color: #f7f7f7;
background-image: -webkit-gradient(linear, left top, left bottom, from(#f7f7f7), to(#e7e7e7));
background-image: -webkit-linear-gradient(top, #f7f7f7, #e7e7e7);
background-image: -moz-linear-gradient(top, #f7f7f7, #e7e7e7);
background-image: -ms-linear-gradient(top, #f7f7f7, #e7e7e7);
background-image: -o-linear-gradient(top, #f7f7f7, #e7e7e7);
color: black;
width: 2.2em;
height: 2.2em;
padding: 0.1em;
position: relative;
text-align: center;
border-radius: 50%;
box-shadow: 0px 3px 8px #aaa, inset 0px 2px 3px #fff;
border: solid 1px transparent;
font-size: 2.2em;
}
/* ---- NAVIGATION ----- */
.btn-nav{
margin: 0;
font-size: 13em;
color: lightgray;
}
.btn-nav:hover{
color: white;
}
.btn-nav:active{
color: dodgerblue;
}
.btn-nav-center{
font-size: 5em;
top: -20px;
color: dodgerblue;
}
.btn-center-unactive{
font-size: 2em;
color: #434343;
}
.pad-img{
padding: 0;
}
.img-cctv{
box-shadow: rgba(14, 30, 37, 0.12) 0px 2px 4px 0px, rgba(14, 30, 37, 0.32) 0px 2px 16px 0px;
border-radius: 10px;
margin: 1.5em;
}
.btn-circle{
width: 20em;
height: 20em;
background-color: black;
align-items: center;
align-content: center;
margin: 5%;
border-radius: 70%;
box-shadow: rgba(0, 0, 0, 0.2) 0px 12px 28px 0px, rgba(0, 0, 0, 0.1) 0px 2px 4px 0px, rgba(255, 255, 255, 0.05) 0px 0px 0px 1px inset;
}
.pad-top{
margin-top: -40px;
margin-bottom: -45px;
}
.pad-left{
margin-top: -65px;
margin-right: -40px;
}
.pad-right{
margin-top: -65px;
margin-left: -40px;
}
.pad-bottom{
margin-top: -110px;
}
#set_zoom{
padding: .5rem 1rem;
font-size: 1.7rem;
border-radius: .3rem;
}
.footer{
font-size: 1.5em;
}
.p-title{
font-size: 3.5em !important;
}
.nav-setting{
font-size: 1.8em;
line-height: 2em;
}
.nav-logout{
font-size: 1.8em !important;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 20px;
background: #ddd;
border-radius: 10px;
}
input[type="range"]::-webkit-slider-runnable-track {
height: 20px;
background: #dddddd;
border-radius: 10px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 30px;
height: 30px;
background: #0d6efd;
border-radius: 50%;
cursor: pointer;
}
.output{
padding-left: 20%;
font-size: 1.8em;
}
.text-setting{
font-size: 1.7em;
}
.text-input{
font-size: 0.9em !important;
}
#icon_confirm,
#icon_password,
#icon_camera{
font-size: 1.5em;
}
.btn-setting{
padding: .5rem 1rem !important;
font-size: 1.1rem !important;
border-radius: .3rem !important;
}
.setting-title{
font-size: 1.3em;
}
span.navbar-toggler-icon {
font-size: 1.8em;
}
}

View File

@@ -28,11 +28,11 @@
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item pad-menu"> <li class="nav-item pad-menu">
<a class="nav-link" href="setting.html">Setting</a> <a class="nav-link nav-setting" href="setting.html">Setting</a>
</li> </li>
<li class="nav-item pad-menu"> <li class="nav-item pad-menu">
<form action="/logout" method="get"> <form action="/logout" method="get">
<button class="btn btn-outline-light" type="submit">Logout</button> <button class="btn btn-outline-light nav-logout" type="submit">Logout</button>
</form> </form>
</li> </li>
</ul> </ul>
@@ -46,7 +46,7 @@
<div class="container"> <div class="container">
<div class="card card-shadow"> <div class="card card-shadow">
<div class="card-header bg-secondary text-light"> <div class="card-header bg-secondary text-light">
<h5 class="text-md-center">Audio Files</h5> <h5 class="text-md-center setting-title">Audio Files</h5>
</div> </div>
<div class="card-body bg-gray"> <div class="card-body bg-gray">
<!-- PRESET 1--> <!-- PRESET 1-->
@@ -55,7 +55,7 @@
<p>Preset 1</p> <p>Preset 1</p>
</div> </div>
<div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6"> <div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6">
<select id="preset1" class="form-control class100"> <select id="preset1" class="form-control text-input class100">
<option>Option 1</option> <option>Option 1</option>
<option>Option 2</option> <option>Option 2</option>
</select> </select>
@@ -67,7 +67,7 @@
<p>Preset 2</p> <p>Preset 2</p>
</div> </div>
<div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6"> <div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6">
<select id="preset2" class="form-control class100"> <select id="preset2" class="form-control text-input class100">
<option>Option 1</option> <option>Option 1</option>
<option>Option 2</option> <option>Option 2</option>
</select> </select>
@@ -79,7 +79,7 @@
<p>Preset 3</p> <p>Preset 3</p>
</div> </div>
<div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6"> <div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6">
<select id="preset3" class="form-control class100"> <select id="preset3" class="form-control text-input class100">
<option>Option 1</option> <option>Option 1</option>
<option>Option 2</option> <option>Option 2</option>
</select> </select>
@@ -91,7 +91,7 @@
<p>Preset 4</p> <p>Preset 4</p>
</div> </div>
<div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6"> <div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6">
<select id="preset4" class="form-control class100"> <select id="preset4" class="form-control text-input class100">
<option>Option 1</option> <option>Option 1</option>
<option>Option 2</option> <option>Option 2</option>
</select> </select>
@@ -103,7 +103,7 @@
<p>Preset 5</p> <p>Preset 5</p>
</div> </div>
<div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6"> <div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6">
<select id="preset5" class="form-control class100"> <select id="preset5" class="form-control text-input class100">
<option>Option 1</option> <option>Option 1</option>
<option>Option 2</option> <option>Option 2</option>
</select> </select>
@@ -112,7 +112,7 @@
<div class="row mt-2"> <div class="row mt-2">
<div class="col-6"></div> <div class="col-6"></div>
<div class="col-6"> <div class="col-6">
<button id="save_audio" class="btn btn-primary class100" onclick="save_audio()">SAVE</button> <button id="save_audio" class="btn btn-primary btn-setting class100" onclick="save_audio()">SAVE</button>
</div> </div>
</div> </div>
</div> </div>
@@ -120,7 +120,7 @@
<div class="card mt-5 card-shadow"> <div class="card mt-5 card-shadow">
<div class="card-header bg-secondary text-light"> <div class="card-header bg-secondary text-light">
<h5 class="text-center"> Upload Audio</h5> <h5 class="text-center setting-title"> Upload Audio</h5>
</div> </div>
<div class="card-body bg-gray"> <div class="card-body bg-gray">
<form action="/setting/uploadaudiofile" method="post" enctype="multipart/form-data"> <form action="/setting/uploadaudiofile" method="post" enctype="multipart/form-data">
@@ -129,10 +129,10 @@
<p> Upload audio files</p> <p> Upload audio files</p>
</div> </div>
<div class="col-6 col-sm-6 col-md-4 col-lg-4 col-xl-4"> <div class="col-6 col-sm-6 col-md-4 col-lg-4 col-xl-4">
<input class="form-control" type="file" name="file" required title="Select Audio File"> <input class="form-control text-input" type="file" name="file" required title="Select Audio File">
</div> </div>
<div class="col-6 col-sm-6 col-md-2 col-lg-2 col-xl-2"> <div class="col-6 col-sm-6 col-md-2 col-lg-2 col-xl-2">
<button id="uploadd_file" type="submit" class="btn btn-dark class100">Upload</button> <button id="uploadd_file" type="submit" class="btn btn-setting btn-dark class100">Upload</button>
</div> </div>
</div> </div>
</form> </form>
@@ -142,7 +142,7 @@
<div class="card mt-5 card-shadow"> <div class="card mt-5 card-shadow">
<div class="card-header bg-secondary text-light "> <div class="card-header bg-secondary text-light ">
<h5 class="text-center">Camera</h5> <h5 class="text-center setting-title">Camera</h5>
</div> </div>
<div class="card-body bg-gray"> <div class="card-body bg-gray">
<div class="row mt-2"> <div class="row mt-2">
@@ -150,7 +150,7 @@
<p>IP Address</p> <p>IP Address</p>
</div> </div>
<div class="col-6"> <div class="col-6">
<input id="setting_ip" name="ip" class="form-control" title="IP Address"> <input id="setting_ip" name="ip" class="form-control text-input" title="IP Address">
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-2">
@@ -158,7 +158,7 @@
<p>Port</p> <p>Port</p>
</div> </div>
<div class="col-6"> <div class="col-6">
<input id="setting_port" name="port" class="form-control" title="Port"> <input id="setting_port" name="port" class="form-control text-input" title="Port">
</div> </div>
</div> </div>
@@ -167,7 +167,7 @@
<p>Username</p> <p>Username</p>
</div> </div>
<div class="col-6"> <div class="col-6">
<input id="setting_username" name="username" class="form-control" title="Username"> <input id="setting_username" name="username" class="form-control text-input" title="Username">
</div> </div>
</div> </div>
@@ -177,7 +177,7 @@
</div> </div>
<div class="col-6"> <div class="col-6">
<div class="input-group"> <div class="input-group">
<input type="password" id="setting_password" name="password" class="form-control" title="Password"> <input type="password" id="setting_password" name="password" class="form-control text-input" title="Password">
<span class="input-group-text" onclick="cameraPassword()"> <span class="input-group-text" onclick="cameraPassword()">
<i class="fa-solid fa-eye" id="icon_camera"></i> <i class="fa-solid fa-eye" id="icon_camera"></i>
</span> </span>
@@ -187,7 +187,7 @@
<div class="row mt-2"> <div class="row mt-2">
<div class="col-6"></div> <div class="col-6"></div>
<div class="col-6"> <div class="col-6">
<button id="save_camera" class="btn btn-primary class100" onclick="save_camera()">SAVE</button> <button id="save_camera" class="btn btn-primary btn-setting class100" onclick="save_camera()">SAVE</button>
</div> </div>
</div> </div>
</div> </div>
@@ -195,7 +195,7 @@
<div class="card mt-5 card-shadow"> <div class="card mt-5 card-shadow">
<div class="card-header bg-secondary text-light"> <div class="card-header bg-secondary text-light">
<h5 class="text-center">Login</h5> <h5 class="text-center setting-title">Login</h5>
</div> </div>
<div class="card-body bg-gray"> <div class="card-body bg-gray">
<div class="row mt-2"> <div class="row mt-2">
@@ -203,7 +203,7 @@
<p>Username</p> <p>Username</p>
</div> </div>
<div class="col-6"> <div class="col-6">
<input id="login_username" class="form-control" title="Username"> <input id="login_username" class="form-control text-input" title="Username">
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-2">
@@ -212,7 +212,7 @@
</div> </div>
<div class="col-6"> <div class="col-6">
<div class="input-group"> <div class="input-group">
<input type="password" id="edit_password" class="form-control" title="Password"> <input type="password" id="edit_password" class="form-control text-input" title="Password">
<span class="input-group-text" onclick="showPassword()"> <span class="input-group-text" onclick="showPassword()">
<i class="fa-solid fa-eye" id="icon_password"></i> <i class="fa-solid fa-eye" id="icon_password"></i>
</span> </span>
@@ -225,7 +225,7 @@
</div> </div>
<div class="col-6"> <div class="col-6">
<div class="input-group"> <div class="input-group">
<input type="password" id="confirm_password" class="form-control" title="Confirm Password"> <input type="password" id="confirm_password" class="form-control text-input" title="Confirm Password">
<span class="input-group-text" onclick="showConfirm()"> <span class="input-group-text" onclick="showConfirm()">
<i class="fa-solid fa-eye" id="icon_confirm"></i> <i class="fa-solid fa-eye" id="icon_confirm"></i>
</span> </span>
@@ -235,7 +235,7 @@
<div class="row mt-2"> <div class="row mt-2">
<div class="col-6"></div> <div class="col-6"></div>
<div class="col-6"> <div class="col-6">
<button id="save_login" class="btn btn-primary class100" onclick="save_login()">SAVE</button> <button id="save_login" class="btn btn-primary btn-setting class100" onclick="save_login()">SAVE</button>
</div> </div>
</div> </div>
</div> </div>
@@ -245,7 +245,7 @@
<!-- SECTION 3 --> <!-- SECTION 3 -->
<div class="container-fluid footer1 outside"> <div class="container-fluid footer1 outside">
<p>2024 Copyright &copy; Galva Technologies. All rights reserved.</p> <p class="footer">2024 Copyright &copy; Galva Technologies. All rights reserved.</p>
</div> </div>
</body> </body>
</html> </html>

21
pom.xml
View File

@@ -7,6 +7,13 @@
<groupId>id.co.gtc</groupId> <groupId>id.co.gtc</groupId>
<artifactId>BirdStrikeSoetta</artifactId> <artifactId>BirdStrikeSoetta</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<repositories>
<repository>
<name>Maven Repository</name>
<id>mvnrepository</id>
<url>https://mvnrepository.com/</url>
</repository>
</repositories>
<dependencies> <dependencies>
<dependency> <dependency>
@@ -111,6 +118,14 @@
<groupId>org.bytedeco</groupId> <groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId> <artifactId>ffmpeg-platform</artifactId>
</exclusion> </exclusion>
<exclusion>
<groupId>org.bytedeco</groupId>
<artifactId>flandmark</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco</groupId>
<artifactId>flandmark-platform</artifactId>
</exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<!-- manual tambah untuk platform windows-x86_64 --> <!-- manual tambah untuk platform windows-x86_64 -->
@@ -189,7 +204,11 @@
<classifier>linux-x86_64</classifier> <classifier>linux-x86_64</classifier>
</dependency> </dependency>
--> -->
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>2.0.12</version>
</dependency>
<dependency> <dependency>
<groupId>io.javalin</groupId> <groupId>io.javalin</groupId>
<artifactId>javalin</artifactId> <artifactId>javalin</artifactId>

View File

@@ -12,18 +12,28 @@ import org.bytedeco.javacv.FrameGrabber;
import org.tinylog.Logger; import org.tinylog.Logger;
public class GrabbingTask implements Runnable { public class GrabbingTask implements Runnable {
// for while loop control
private final AtomicBoolean isGrabbing;
// for grabbing frames
private final FrameGrabber grabber;
// Consumers
@Setter private Consumer<String> onMessageUpdate; @Setter private Consumer<String> onMessageUpdate;
@Setter private Consumer<Frame> onHQFrameUpdate; @Setter private Consumer<Frame> onHQFrameUpdate;
@Setter private Consumer<Frame> onLQFrameUpdate; @Setter private Consumer<Frame> onLQFrameUpdate;
@Setter private Consumer<String> onHQBase64Update; @Setter private Consumer<String> onHQBase64Update;
@Setter private Consumer<String> onLQBase64Update; @Setter private Consumer<String> onLQBase64Update;
// status of capture fps
@Getter private int CaptureFPS = 0; @Getter private int CaptureFPS = 0;
private final AtomicBoolean isGrabbing; @Getter @Setter private int lowquality_width = 640;
private final FrameGrabber grabber; @Getter @Setter private int lowquality_height = 360;
@Getter private final int lowquality_width = 640;
@Getter private final int lowquality_height = 360;
// for FPS calculation
private int intendedFps = 10;
@SuppressWarnings("SameParameterValue")
private void updateMessage(String message) { private void updateMessage(String message) {
if (onMessageUpdate != null) { if (onMessageUpdate != null) {
onMessageUpdate.accept(message); onMessageUpdate.accept(message);
@@ -40,6 +50,7 @@ public class GrabbingTask implements Runnable {
} }
} }
@SuppressWarnings("SameParameterValue")
private void updateLQBase64(String base64) { private void updateLQBase64(String base64) {
if (onLQBase64Update != null) { if (onLQBase64Update != null) {
onLQBase64Update.accept(base64); onLQBase64Update.accept(base64);
@@ -62,10 +73,10 @@ public class GrabbingTask implements Runnable {
public GrabbingTask(AtomicBoolean isGrabbing, FrameGrabber grabber) { public GrabbingTask(AtomicBoolean isGrabbing, FrameGrabber grabber, int fps) {
this.isGrabbing = isGrabbing; this.isGrabbing = isGrabbing;
this.grabber = grabber; this.grabber = grabber;
if (fps>0) intendedFps = fps;
} }
@@ -73,39 +84,51 @@ public class GrabbingTask implements Runnable {
isGrabbing.set(false); isGrabbing.set(false);
} }
private void grabprocess() throws Exception{ private void processFrame(Frame fr){
grabber.flush();
Frame fr =grabber.grab();
if (fr!=null){ if (fr!=null){
updateHQFrame(fr); updateHQFrame(fr);
updateHQBase64(SomeCodes.FrameToBase64(fr)); updateHQBase64(SomeCodes.FrameToBase64(fr));
Frame resized = SomeCodes.ResizeFrame(fr, lowquality_width, lowquality_height); Frame resized = SomeCodes.ResizeFrame(fr, lowquality_width, lowquality_height);
updateLQFrame(resized); updateLQFrame(resized);
updateLQBase64(SomeCodes.FrameToBase64(resized)); updateLQBase64(SomeCodes.FrameToBase64(resized));
} else updateMessage("Grabber returned null frame"); } else updateMessage("processFrame have null frame");
} }
private void flush_grabber(){
try {
if (grabber!=null) grabber.flush();
} catch (FrameGrabber.Exception e) {
Logger.error("Error flushing grabber: "+e.getMessage());
}
}
@Override @Override
public void run() { public void run() {
isGrabbing.set(true); isGrabbing.set(true);
Logger.info("Grabbing Task started"); Logger.info("Grabbing Task started");
double fps = grabber.getFrameRate(); double fps = grabber.getFrameRate();
Logger.info("Grabber framerate = {}", fps); int skippedframes = (int)(fps / intendedFps);
//Logger.info("Grabber framerate = {}, intendedFps = {}, Skipping frames = {}", fps, intendedFps, skippedframes);
int framecount = 0;
flush_grabber();
long starttick = System.currentTimeMillis(); long starttick = System.currentTimeMillis();
while (isGrabbing.get()) { while (isGrabbing.get()) {
long elapsed = System.currentTimeMillis() - starttick;
starttick = System.currentTimeMillis();
//Logger.info("Elapsed time = {} ms", elapsed);
if (elapsed>0) CaptureFPS = (int) (1000 / elapsed);
try{ try{
Thread.yield(); Thread.yield();
grabprocess(); Frame frame = grabber.grab();
if (framecount<Integer.MAX_VALUE) framecount++; else framecount = 0;
if (framecount % skippedframes == 0) processFrame(frame);
} catch (Exception e){ } catch (Exception e){
Logger.error("Error grabbing frame: "+e.getMessage()); Logger.error("Error grabbing frame: "+e.getMessage());
} }
long elapsed = System.currentTimeMillis() - starttick;
starttick = System.currentTimeMillis();
if (elapsed>0) {
int xx = (int) (1000 / elapsed);
if (xx<fps) CaptureFPS = xx;
}
//Logger.info("Elapsed time = {} ms, captureFPS = {}", elapsed, CaptureFPS);
} }
Logger.info("Grabbing Task stopped"); Logger.info("Grabbing Task stopped");
} }

View File

@@ -1,6 +1,7 @@
package Camera; package Camera;
import com.fazecast.jSerialComm.SerialPort; import com.fazecast.jSerialComm.SerialPort;
import id.co.gtc.Main;
import org.tinylog.Logger; import org.tinylog.Logger;
@@ -23,12 +24,14 @@ public class PanTiltController {
if (comports.length>0){ if (comports.length>0){
for (SerialPort port : comports){ for (SerialPort port : comports){
Logger.info("Available Serial Port : {}", port.getSystemPortName()); Logger.info("Available Serial Port : {}", port.getSystemPortName());
if (port.getSystemPortName().equals(portname)){
if (portname.contains(port.getSystemPortName())){
Logger.info("Serial Port {} found", portname); Logger.info("Serial Port {} found", portname);
serialPort = port; serialPort = port;
break; break;
} }
} }
if (serialPort!=null){ if (serialPort!=null){
serialPort.setBaudRate(baudrate); serialPort.setBaudRate(baudrate);
@@ -40,9 +43,6 @@ public class PanTiltController {
} else Logger.info("Serial Port {} not found", portname); } else Logger.info("Serial Port {} not found", portname);
} else Logger.info("No Serial Port found"); } else Logger.info("No Serial Port found");
} }
/** /**
@@ -53,6 +53,14 @@ public class PanTiltController {
Logger.info("Serial Port closed"); Logger.info("Serial Port closed");
} }
/**
* Check if Serial Port is open
* @return true if open, false otherwise
*/
public boolean isOpen(){
return serialPort != null && serialPort.isOpen();
}
/** /**
* Stop Pan Tilt Movement * Stop Pan Tilt Movement
*/ */
@@ -60,7 +68,12 @@ public class PanTiltController {
byte[] command = new byte[]{0, cameraid, 0, 0, 0, 0, 0}; byte[] command = new byte[]{0, cameraid, 0, 0, 0, 0, 0};
command[6] = Checksum(command); // add checksum command[6] = Checksum(command); // add checksum
command[0] = (byte) 0xFF; // add synchronization byte command[0] = (byte) 0xFF; // add synchronization byte
if (isOpen()) {
Main.Max485Direction(true);
serialPort.writeBytes(command, command.length); serialPort.writeBytes(command, command.length);
Main.Max485Direction(false);
Main.Blink_LedPanTilt();
}
} }
/** /**
@@ -74,7 +87,12 @@ public class PanTiltController {
byte[] command = new byte[]{0, cameraid, 0, 4, speed, 0, 0}; byte[] command = new byte[]{0, cameraid, 0, 4, speed, 0, 0};
command[6] = Checksum(command); // add checksum command[6] = Checksum(command); // add checksum
command[0] = (byte) 0xFF; // add synchronization byte command[0] = (byte) 0xFF; // add synchronization byte
if (isOpen()) {
Main.Max485Direction(true);
serialPort.writeBytes(command, command.length); serialPort.writeBytes(command, command.length);
Main.Max485Direction(false);
Main.Blink_LedPanTilt();
}
} }
/** /**
@@ -88,7 +106,12 @@ public class PanTiltController {
byte[] command = new byte[]{0, cameraid, 0, 2, speed, 0, 0}; byte[] command = new byte[]{0, cameraid, 0, 2, speed, 0, 0};
command[6] = Checksum(command); // add checksum command[6] = Checksum(command); // add checksum
command[0] = (byte) 0xFF; // add synchronization byte command[0] = (byte) 0xFF; // add synchronization byte
if (isOpen()) {
Main.Max485Direction(true);
serialPort.writeBytes(command, command.length); serialPort.writeBytes(command, command.length);
Main.Max485Direction(false);
Main.Blink_LedPanTilt();
}
} }
/** /**
@@ -102,7 +125,12 @@ public class PanTiltController {
byte[] command = new byte[]{0, cameraid, 0, 8, speed, 0, 0}; byte[] command = new byte[]{0, cameraid, 0, 8, speed, 0, 0};
command[6] = Checksum(command); // add checksum command[6] = Checksum(command); // add checksum
command[0] = (byte) 0xFF; // add synchronization byte command[0] = (byte) 0xFF; // add synchronization byte
if (isOpen()) {
Main.Max485Direction(true);
serialPort.writeBytes(command, command.length); serialPort.writeBytes(command, command.length);
Main.Max485Direction(false);
Main.Blink_LedPanTilt();
}
} }
/** /**
@@ -116,7 +144,12 @@ public class PanTiltController {
byte[] command = new byte[]{0, cameraid, 0, 16, speed, 0, 0}; byte[] command = new byte[]{0, cameraid, 0, 16, speed, 0, 0};
command[6] = Checksum(command); // add checksum command[6] = Checksum(command); // add checksum
command[0] = (byte) 0xFF; // add synchronization byte command[0] = (byte) 0xFF; // add synchronization byte
if (isOpen()) {
Main.Max485Direction(true);
serialPort.writeBytes(command, command.length); serialPort.writeBytes(command, command.length);
Main.Max485Direction(false);
Main.Blink_LedPanTilt();
}
} }
/** /**

View File

@@ -3,12 +3,14 @@ import lombok.Getter;
import org.bytedeco.ffmpeg.global.avutil; import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber; import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame; import org.bytedeco.javacv.Frame;
import org.jetbrains.annotations.NotNull;
import org.tinylog.Logger; import org.tinylog.Logger;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import static Other.SomeCodes.gson; import static Other.SomeCodes.gson;
@SuppressWarnings("unused")
public class RtspGrabber { public class RtspGrabber {
private final String rtspUrl; private final String rtspUrl;
private FFmpegFrameGrabber grabber; private FFmpegFrameGrabber grabber;
@@ -69,40 +71,14 @@ public class RtspGrabber {
try{ try{
grabber = FFmpegFrameGrabber.createDefault(rtspUrl); grabber = FFmpegFrameGrabber.createDefault(rtspUrl);
if (useTcp) grabber.setOption("rtsp_transport", "tcp"); if (useTcp) grabber.setOption("rtsp_transport", "tcp");
grabber.setTimeout(2000);
grabber.setImageWidth(width);
grabber.setImageHeight(height);
grabber.setPixelFormat(avutil.AV_PIX_FMT_BGR24); grabber.setPixelFormat(avutil.AV_PIX_FMT_BGR24);
grabber.start(); grabber.start();
avutil.av_log_set_level(avutil.AV_LOG_ERROR); avutil.av_log_set_level(avutil.AV_LOG_ERROR);
Logger.info("Grabber started"); Logger.info("Grabber started");
GrabbingTask tt = new GrabbingTask(isGrabbing, grabber); GrabbingTask tt = getGrabbingTask();
tt.setOnMessageUpdate(Logger::info);
tt.setOnHQFrameUpdate(value -> {
if (value!=null){
if (value.imageWidth>0 && value.imageHeight>0){
setLastHQFrame(value);
HQWidth = value.imageWidth;
HQHeight = value.imageHeight;
}
}
});
tt.setOnLQFrameUpdate(value -> {
if (value!=null){
if (value.imageWidth>0 && value.imageHeight>0){
setLastLQFrame(value);
LQWidth = value.imageWidth;
LQHeight = value.imageHeight;
}
}
});
tt.setOnHQBase64Update(this::setLastHQBase64);
tt.setOnLQBase64Update(this::setLastLQBase64);
grabbingTask = tt; grabbingTask = tt;
new Thread(tt).start(); new Thread(tt).start();
@@ -111,6 +87,8 @@ public class RtspGrabber {
} }
} }
/** /**
* Stop grabbing frames * Stop grabbing frames
*/ */
@@ -133,10 +111,38 @@ public class RtspGrabber {
} }
public String LQStreamingStatus(){ public String LQStreamingStatus(){
return gson.toJson(new String[]{String.valueOf(LQWidth), String.valueOf(LQHeight) , String.valueOf(grabbingTask.getCaptureFPS())}); return gson.toJson(new String[]{String.valueOf(LQWidth), String.valueOf(LQHeight) , String.valueOf(grabbingTask!=null ? grabbingTask.getCaptureFPS():"0")});
} }
public String HQStreamingStatus(){ public String HQStreamingStatus(){
return gson.toJson(new String[]{String.valueOf(HQWidth), String.valueOf(HQHeight) , String.valueOf(grabbingTask.getCaptureFPS())}); return gson.toJson(new String[]{String.valueOf(HQWidth), String.valueOf(HQHeight) , String.valueOf(grabbingTask!=null ? grabbingTask.getCaptureFPS(): "0")});
}
@NotNull
private GrabbingTask getGrabbingTask() {
GrabbingTask tt = new GrabbingTask(isGrabbing, grabber,10);
tt.setOnMessageUpdate(Logger::info);
tt.setOnHQFrameUpdate(value -> {
if (value!=null){
if (value.imageWidth>0 && value.imageHeight>0){
setLastHQFrame(value);
HQWidth = value.imageWidth;
HQHeight = value.imageHeight;
}
}
});
tt.setOnLQFrameUpdate(value -> {
if (value!=null){
if (value.imageWidth>0 && value.imageHeight>0){
setLastLQFrame(value);
LQWidth = value.imageWidth;
LQHeight = value.imageHeight;
}
}
});
tt.setOnHQBase64Update(this::setLastHQBase64);
tt.setOnLQBase64Update(this::setLastLQBase64);
return tt;
} }
} }

View File

@@ -1,5 +1,6 @@
package Camera; package Camera;
import lombok.Getter;
import org.tinylog.Logger; import org.tinylog.Logger;
import java.net.http.HttpClient; import java.net.http.HttpClient;
@@ -27,7 +28,7 @@ public class VapixProtocol {
private String[] ImageResolutions = new String[0]; private String[] ImageResolutions = new String[0];
private String[] ImageFormats = new String[0]; private String[] ImageFormats = new String[0];
private int CurrentZoomValue = 0; private int CurrentZoomValue = 0;
@Getter private boolean successQuery = false;
/** /**
* Create a new VapixProtocol object * Create a new VapixProtocol object
* @param ip The IP address of the camera * @param ip The IP address of the camera
@@ -83,6 +84,7 @@ public class VapixProtocol {
if (ValidString(value)) ImageFormats = value.split(","); if (ValidString(value)) ImageFormats = value.split(",");
//Logger.info("Format: "+value); //Logger.info("Format: "+value);
} }
successQuery = true;
} }
} }

View File

@@ -44,6 +44,9 @@ public class SomeCodes {
public static boolean useOpenCL; public static boolean useOpenCL;
private static final Base64.Encoder base64encoder = java.util.Base64.getEncoder(); private static final Base64.Encoder base64encoder = java.util.Base64.getEncoder();
public static final Gson gson = new Gson(); public static final Gson gson = new Gson();
public static final double KB_threshold = 1024.0;
public static final double MB_threshold = 1024.0 * 1024.0;
public static final double GB_threshold = 1024.0 * 1024.0 * 1024.0;
public static String[] GetAudioFiles(){ public static String[] GetAudioFiles(){
try{ try{

View File

@@ -1,6 +1,7 @@
package SBC; package SBC;
import com.sun.jna.Platform; import com.sun.jna.Platform;
import org.jetbrains.annotations.NotNull;
import org.tinylog.Logger; import org.tinylog.Logger;
import java.nio.file.Files; import java.nio.file.Files;
@@ -8,7 +9,7 @@ import java.nio.file.Path;
public class GPIO { public class GPIO {
private static final Path gpioPath = Path.of("/sys/class/gpio"); public static final Path gpioPath = Path.of("/sys/class/gpio");
private static final Path gpioExportPath = Path.of("/sys/class/gpio/export"); private static final Path gpioExportPath = Path.of("/sys/class/gpio/export");
private static final Path gpioUnexportPath = Path.of("/sys/class/gpio/unexport"); private static final Path gpioUnexportPath = Path.of("/sys/class/gpio/unexport");
@@ -31,8 +32,8 @@ public class GPIO {
* @param pin GPIO pin number * @param pin GPIO pin number
* @return true if the pin is already exported * @return true if the pin is already exported
*/ */
public static boolean GpioPinExists(int pin){ public static boolean GpioPinExists(@NotNull RaspberryPi5BPins pin){
Path pinPath = gpioPath.resolve("gpio"+pin); Path pinPath = gpioPath.resolve("gpio"+pin.gpionumber);
return pinPath.toFile().isDirectory(); return pinPath.toFile().isDirectory();
} }
@@ -41,15 +42,15 @@ public class GPIO {
* @param pin GPIO pin number * @param pin GPIO pin number
* @return true if the pin is successfully exported * @return true if the pin is successfully exported
*/ */
public static boolean ExportPin(int pin){ public static boolean ExportPin(@NotNull RaspberryPi5BPins pin){
try{ try{
if (Files.isWritable(gpioExportPath)){ if (Files.isWritable(gpioExportPath)){
Files.write(gpioExportPath, String.valueOf(pin).getBytes()); Files.write(gpioExportPath, String.valueOf(pin.gpionumber).getBytes());
Logger.info("Pin "+pin+" exported"); Logger.info("Pin "+pin.pin+" exported");
return GpioPinExists(pin); return GpioPinExists(pin);
} else Logger.error("GPIO export path is not writable"); } else Logger.error("GPIO export path is not writable");
} catch (Exception e){ } catch (Exception e){
Logger.error("Failed to export pin: "+pin+", Message: "+e.getMessage()); Logger.error("Failed to export pin: "+pin.pin+", Message: "+e.getMessage());
} }
return false; return false;
} }
@@ -59,14 +60,14 @@ public class GPIO {
* @param pin GPIO pin number * @param pin GPIO pin number
* @return true if the pin is successfully unexported * @return true if the pin is successfully unexported
*/ */
public static boolean UnexportPin(int pin){ public static boolean UnexportPin(@NotNull RaspberryPi5BPins pin){
if (Files.isWritable(gpioUnexportPath)){ if (Files.isWritable(gpioUnexportPath)){
try{ try{
Files.write(gpioUnexportPath, String.valueOf(pin).getBytes()); Files.write(gpioUnexportPath, String.valueOf(pin.gpionumber).getBytes());
Logger.info("Pin "+pin+" unexported"); Logger.info("Pin "+pin.pin+" unexported");
return true; return true;
} catch (Exception e){ } catch (Exception e){
Logger.error("Failed to unexport pin: "+pin+", Message: "+e.getMessage()); Logger.error("Failed to unexport pin: "+pin.pin+", Message: "+e.getMessage());
} }
} else Logger.error("GPIO unexport path is not writable"); } else Logger.error("GPIO unexport path is not writable");
@@ -78,17 +79,18 @@ public class GPIO {
* @param pin GPIO pin number * @param pin GPIO pin number
* @return "in" if the pin is input, "out" if the pin is output, "unknown" if the direction is unknown * @return "in" if the pin is input, "out" if the pin is output, "unknown" if the direction is unknown
*/ */
public static String GetPinDirection(int pin){ @SuppressWarnings("unused")
Path pinPath = gpioPath.resolve("gpio"+pin).resolve("direction"); public static String GetPinDirection(@NotNull RaspberryPi5BPins pin){
Path pinPath = gpioPath.resolve("gpio"+pin.gpionumber).resolve("direction");
if (pinPath.toFile().isFile()){ if (pinPath.toFile().isFile()){
if (Files.isReadable(pinPath)){ if (Files.isReadable(pinPath)){
try{ try{
return Files.readString(pinPath).trim(); return Files.readString(pinPath).trim();
} catch (Exception e){ } catch (Exception e){
Logger.error("Failed to read pin direction: "+pin+", Message: "+e.getMessage()); Logger.error("Failed to read pin direction: "+pin.pin+", Message: "+e.getMessage());
} }
} else Logger.error("Pin direction file is not readable: "+pin); } else Logger.error("Pin direction file is not readable: "+pin.pin);
} else Logger.error("Pin direction file not found: "+pin); } else Logger.error("Pin direction file not found: "+pin.pin);
return "unknown"; return "unknown";
} }
@@ -98,22 +100,22 @@ public class GPIO {
* @param direction "in" for input, "out" for output * @param direction "in" for input, "out" for output
* @return true if the direction is successfully set * @return true if the direction is successfully set
*/ */
public static boolean SetPinDirection(int pin, String direction){ public static boolean SetPinDirection(@NotNull RaspberryPi5BPins pin, String direction){
Path pinPath = gpioPath.resolve("gpio"+pin).resolve("direction"); Path pinPath = gpioPath.resolve("gpio"+pin.gpionumber).resolve("direction");
if (pinPath.toFile().isFile()){ if (pinPath.toFile().isFile()){
if (Files.isWritable(pinPath)){ if (Files.isWritable(pinPath)){
direction = direction.trim().toLowerCase(); direction = direction.trim().toLowerCase();
if ("in".equals(direction) || "out".equals(direction)){ if ("in".equals(direction) || "out".equals(direction)){
try{ try{
Files.write(pinPath, direction.getBytes()); Files.write(pinPath, direction.getBytes());
Logger.info("Pin "+pin+" direction set to "+direction); Logger.info("Pin "+pin.pin+" direction set to "+direction);
return true; return true;
} catch (Exception e){ } catch (Exception e){
Logger.error("Failed to set pin direction: "+pin+", Message: "+e.getMessage()); Logger.error("Failed to set pin direction: "+pin.pin+", Message: "+e.getMessage());
} }
} else Logger.error("Invalid direction: "+direction); } else Logger.error("Invalid direction: "+direction);
} else Logger.error("Pin direction file is not writable: "+pin); } else Logger.error("Pin direction file is not writable: "+pin.pin);
} else Logger.error("Pin direction file not found: "+pin); } else Logger.error("Pin direction file not found: "+pin.pin);
return false; return false;
} }
@@ -123,19 +125,20 @@ public class GPIO {
* @param isON true to set the pin value to 1, false to set the pin value to 0 * @param isON true to set the pin value to 1, false to set the pin value to 0
* @return true if the value is successfully set * @return true if the value is successfully set
*/ */
public static boolean SetValue(int pin, boolean isON){ @SuppressWarnings("UnusedReturnValue")
Path pinPath = gpioPath.resolve("gpio"+pin).resolve("value"); public static boolean SetValue(@NotNull RaspberryPi5BPins pin, boolean isON){
Path pinPath = gpioPath.resolve("gpio"+pin.gpionumber).resolve("value");
if (pinPath.toFile().isFile()){ if (pinPath.toFile().isFile()){
if (Files.isWritable(pinPath)){ if (Files.isWritable(pinPath)){
try{ try{
Files.write(pinPath, isON?"1".getBytes():"0".getBytes()); Files.write(pinPath, isON?"1".getBytes():"0".getBytes());
Logger.info("Pin "+pin+" value set to "+(isON?"1":"0")); //Logger.info("Pin "+pin.pin+" value set to "+(isON?"1":"0"));
return true; return true;
} catch (Exception e){ } catch (Exception e){
Logger.error("Failed to set pin value: "+pin+", Message: "+e.getMessage()); Logger.error("Failed to set pin value: "+pin.pin+", Message: "+e.getMessage());
} }
} else Logger.error("Pin value file is not writable: "+pin); } else Logger.error("Pin value file is not writable: "+pin.pin);
} else Logger.error("Pin value file not found: "+pin); } else Logger.error("Pin value file not found: "+pin.pin);
return false; return false;
} }
@@ -144,17 +147,18 @@ public class GPIO {
* @param pin GPIO pin number * @param pin GPIO pin number
* @return "1" if the pin value is 1, "0" if the pin value is 0, "unknown" if the value is unknown * @return "1" if the pin value is 1, "0" if the pin value is 0, "unknown" if the value is unknown
*/ */
public static String GetValue(int pin){ @SuppressWarnings("unused")
Path pinPath = gpioPath.resolve("gpio"+pin).resolve("value"); public static String GetValue(@NotNull RaspberryPi5BPins pin){
Path pinPath = gpioPath.resolve("gpio"+pin.gpionumber).resolve("value");
if (pinPath.toFile().isFile()){ if (pinPath.toFile().isFile()){
if (Files.isReadable(pinPath)){ if (Files.isReadable(pinPath)){
try{ try{
return Files.readString(pinPath).trim(); return Files.readString(pinPath).trim();
} catch (Exception e){ } catch (Exception e){
Logger.error("Failed to read pin value: "+pin+", Message: "+e.getMessage()); Logger.error("Failed to read pin value: "+pin.pin+", Message: "+e.getMessage());
} }
} else Logger.error("Pin value file is not readable: "+pin); } else Logger.error("Pin value file is not readable: "+pin.pin);
} else Logger.error("Pin value file not found: "+pin); } else Logger.error("Pin value file not found: "+pin.pin);
return "unknown"; return "unknown";
} }
} }

View File

@@ -0,0 +1,42 @@
package SBC;
public enum RaspberryPi5BPins {
Pin03(3,"GPIO2/SDA", 573),
Pin05(5,"GPIO3/SCL", 574),
Pin07(7,"GPIO4/GPCLK0", 575),
Pin08(8,"GPIO14/TXD", 585),
Pin10(10,"GPIO15/RXD", 586),
Pin11(11,"GPIO17", 588),
Pin12(12,"GPIO18/PCMCLK", 589),
Pin13(13,"GPIO27", 598),
Pin15(15,"GPIO22", 593),
Pin16(16,"GPIO23", 594),
Pin18(18,"GPIO24", 595),
Pin19(19,"GPIO10/MOSI", 581),
Pin21(21,"GPIO9/MISO", 580),
Pin22(22,"GPIO25", 596),
Pin23(23,"GPIO11/SCLK", 582),
Pin24(24,"GPIO8/CE0", 579),
Pin26(26,"GPIO7/CE1", 578),
Pin27(27,"GPIO0/IDSD", 587),
Pin28(28,"GPIO1/IDSC", 587),
Pin29(29,"GPIO5", 576),
Pin31(31,"GPIO6", 577),
Pin32(32,"GPIO12/PWM0", 583),
Pin33(33,"GPIO13/PWM1", 584),
Pin35(35,"GPIO19/PCMFS", 590),
Pin36(36,"GPIO16", 587),
Pin37(37,"GPIO26", 597),
Pin38(38,"GPIO20/PCMDIN", 591),
Pin40(40,"GPIO21/PCMDOUT", 592);
public final int pin;
public final String name;
public final int gpionumber;
RaspberryPi5BPins(int pin, String name, int gpionumber){
this.pin = pin;
this.name = name;
this.gpionumber = gpionumber;
}
}

View File

@@ -1,6 +1,11 @@
package Web; package Web;
import Other.SomeCodes; import Other.SomeCodes;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DisconnectListener;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.http.UploadedFile; import io.javalin.http.UploadedFile;
import io.javalin.util.FileUtil; import io.javalin.util.FileUtil;
@@ -10,10 +15,7 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.tinylog.Logger; import org.tinylog.Logger;
import java.util.List; import java.util.*;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import static Other.SomeCodes.*; import static Other.SomeCodes.*;
@@ -26,10 +28,16 @@ public class WebServer {
private @Getter @Setter String webpassword; private @Getter @Setter String webpassword;
private final Javalin app; private final Javalin app;
private final Set<WsContext> connectedWebsocketClients = ConcurrentHashMap.newKeySet(); private final Set<WsContext> connectedWebsocketClients = ConcurrentHashMap.newKeySet();
private final SocketIOServer socketServer;
private final Configuration socketConfig;
private final HashMap<String, SocketIOClient> socketIOClients = new HashMap<>();
public WebServer(WebsocketEvent event, String webusername, String webpassword){ public WebServer(WebsocketEvent event, String webusername, String webpassword){
this.webusername = webusername; this.webusername = webusername;
this.webpassword = webpassword; this.webpassword = webpassword;
app = Javalin.create(config -> { app = Javalin.create(config -> {
config.useVirtualThreads = true;
config.staticFiles.add("/html"); config.staticFiles.add("/html");
config.router.apiBuilder(()-> path("setting", () ->{ config.router.apiBuilder(()-> path("setting", () ->{
get(ctx -> ctx.json(SettingInfo.getInstance())); get(ctx -> ctx.json(SettingInfo.getInstance()));
@@ -174,10 +182,13 @@ public class WebServer {
ctx.redirect("/login.html"); ctx.redirect("/login.html");
}); });
/*
WebSocket Communication
This is temporary, later must choose either WebSocket or SocketIO
*/
app.ws("/ws", ws -> { app.ws("/ws", ws -> {
ws.onConnect(connectws); ws.onConnect(connectws);
ws.onClose(closews); ws.onClose(closews);
//ws.onError(errorws);
ws.onMessage(ctx -> { ws.onMessage(ctx -> {
try{ try{
//Logger.info("WebSocket message {}", ctx.message()); //Logger.info("WebSocket message {}", ctx.message());
@@ -195,6 +206,29 @@ public class WebServer {
}); });
}); });
/*
SocketIO Communication
This is temporary, later must choose either WebSocket or SocketIO
*/
socketConfig = new Configuration();
socketConfig.setHostname("0.0.0.0");
socketConfig.setPort(3001);
socketServer = new SocketIOServer(socketConfig);
socketServer.addConnectListener(connectListener);
socketServer.addDisconnectListener(disconnectListener);
socketServer.addNamespace("/socketio").addEventListener("message", String.class, (client, data, ackSender) -> {
//Logger.info("SocketIO message from namespace /socketio from {}: {}", client.getRemoteAddress(), data);
WebsocketCommand command = gson.fromJson(data, WebsocketCommand.class);
if (event!=null) {
WebsocketReply reply = event.onWebsocketCommand(command);
if (reply!=null) client.sendEvent("message", gson.toJson(reply));
}
});
} }
/** /**
@@ -202,7 +236,12 @@ public class WebServer {
* @param obj Object to send * @param obj Object to send
*/ */
public void SendtoAll(Object obj){ public void SendtoAll(Object obj){
connectedWebsocketClients.forEach(wsContext -> wsContext.send(obj));
connectedWebsocketClients
.stream()
.filter(ws -> ws.session.isOpen())
.forEach(ws -> ws.send(obj));
socketIOClients.forEach((key, client) -> client.sendEvent("message", gson.toJson(obj)));
} }
/** /**
@@ -212,9 +251,10 @@ public class WebServer {
*/ */
public void Start(String localip, int port){ public void Start(String localip, int port){
try{ try{
connectedWebsocketClients.forEach(WsContext::closeSession);
app.start(localip, port); app.start(localip, port);
Logger.info("Web server started at {}:{}", localip, port); Logger.info("Web server started at {}:{}", localip, port);
socketServer.start();
Logger.info("SocketIO server started at {}:{}", socketConfig.getHostname(), socketConfig.getPort());
} catch (JavalinException e){ } catch (JavalinException e){
Logger.error("Web server failed to start: {}", e.getMessage()); Logger.error("Web server failed to start: {}", e.getMessage());
} }
@@ -225,8 +265,14 @@ public class WebServer {
*/ */
public void Stop(){ public void Stop(){
try{ try{
connectedWebsocketClients.forEach(WsContext::closeSession);
connectedWebsocketClients.clear();
app.stop(); app.stop();
Logger.info("Web server stopped"); Logger.info("Web server stopped");
socketIOClients.forEach((key, client) -> client.disconnect());
socketIOClients.clear();
socketServer.stop();
Logger.info("SocketIO server stopped");
} catch (JavalinException e){ } catch (JavalinException e){
Logger.error("Web server failed to stop: {}", e.getMessage()); Logger.error("Web server failed to stop: {}", e.getMessage());
} }
@@ -243,7 +289,16 @@ public class WebServer {
connectedWebsocketClients.remove(ws); connectedWebsocketClients.remove(ws);
}; };
//WsErrorHandler errorws = ws -> Logger.error("WebSocket error from {}, error {}", ws.host(), ws.error()); ConnectListener connectListener = client -> {
Logger.info("SocketIO connected, id {} from {}", client.getSessionId(), client.getRemoteAddress());
socketIOClients.put(client.getSessionId().toString(), client);
};
DisconnectListener disconnectListener = client -> {
Logger.info("SocketIO disconnected, id {} from {}", client.getSessionId(), client.getRemoteAddress());
socketIOClients.remove(client.getSessionId().toString());
};

View File

@@ -8,9 +8,7 @@ import Camera.PanTiltController;
import Camera.RtspGrabber; import Camera.RtspGrabber;
import Camera.VapixProtocol; import Camera.VapixProtocol;
import Other.SomeCodes; import Other.SomeCodes;
import SBC.ProcessorStatus; import SBC.*;
import SBC.RamInformation;
import SBC.SystemInformation;
import Web.*; import Web.*;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import org.bytedeco.opencv.global.opencv_core; import org.bytedeco.opencv.global.opencv_core;
@@ -19,6 +17,8 @@ import org.tinylog.Logger;
import java.io.File; import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static Other.SomeCodes.*; import static Other.SomeCodes.*;
@@ -34,6 +34,16 @@ public class Main {
private static RamInformation ramInformation; private static RamInformation ramInformation;
private static ProcessorStatus[] previousCpuInfo; private static ProcessorStatus[] previousCpuInfo;
private static final Map<String, Integer> cpuUsage = new HashMap<>(); private static final Map<String, Integer> cpuUsage = new HashMap<>();
private static NetworkTransmitReceiveInfo[] previousNetworkInfo;
private static final Map<String, String> networkTX = new HashMap<>();
private static final Map<String, String> networkRX = new HashMap<>();
private static RaspberryPi5BPins AmplifierPower = null;
private static RaspberryPi5BPins LedWeb = null;
private static RaspberryPi5BPins LedIpCamera = null;
private static RaspberryPi5BPins LedPanTilt = null;
private static RaspberryPi5BPins Max485Direction = null;
private static ExecutorService gpioExecutor = null;
// Application start from here // Application start from here
public static void main(String[] args) { public static void main(String[] args) {
@@ -44,18 +54,100 @@ public class Main {
if (panTiltController!=null) panTiltController.Close(); if (panTiltController!=null) panTiltController.Close();
if (vapixProtocol!=null) vapixProtocol.Close(); if (vapixProtocol!=null) vapixProtocol.Close();
if (timer!=null) timer.cancel(); if (timer!=null) timer.cancel();
if (gpioExecutor!=null) gpioExecutor.shutdown();
if (AmplifierPower!=null) {
Logger.info("GPIO pin {} unexport : {}",AmplifierPower.pin, GPIO.UnexportPin(AmplifierPower));
}
if (LedWeb!=null) {
Logger.info("GPIO pin {} unexport : {}", LedWeb.pin,GPIO.UnexportPin(LedWeb));
}
if (LedIpCamera!=null) {
Logger.info("GPIO pin {} unexport : {}", LedIpCamera.pin, GPIO.UnexportPin(LedIpCamera));
}
if (LedPanTilt!=null) {
Logger.info("GPIO pin {} unexport : {}", LedPanTilt.pin,GPIO.UnexportPin(LedPanTilt));
}
if (Max485Direction!=null) {
Logger.info("GPIO pin {} unexport : {}", Max485Direction.pin, GPIO.UnexportPin(Max485Direction));
}
})); }));
init_gpio();
init_system_monitoring(); init_system_monitoring();
init_properties(); init_properties();
init_audiofiles(); init_audiofiles();
init_Vapix();
init_audio(); init_audio();
init_pantiltcontroller();
init_webserver(); init_webserver();
init_rtspgrabber(); init_rtspgrabber();
init_pantiltcontroller(); }
init_Vapix();
private static void init_gpio(){
if (GPIO.HaveGPIO()){
gpioExecutor = Executors.newVirtualThreadPerTaskExecutor();
AmplifierPower = InitializePin(RaspberryPi5BPins.Pin13, true);
LedWeb = InitializePin(RaspberryPi5BPins.Pin15, true);
LedIpCamera = InitializePin(RaspberryPi5BPins.Pin19, true);
LedPanTilt = InitializePin(RaspberryPi5BPins.Pin21, true);
Max485Direction = InitializePin(RaspberryPi5BPins.Pin23, true);
}
}
@SuppressWarnings("SameParameterValue")
private static RaspberryPi5BPins InitializePin(RaspberryPi5BPins pin, boolean isOutput){
boolean exported = false;
if (GPIO.GpioPinExists(pin)){
exported = true;
} else {
if (GPIO.ExportPin(pin)){
exported = true;
}
}
if (exported){
if (GPIO.SetPinDirection(pin, isOutput?"out":"in")){
return pin;
}
}
return null;
}
// dipanggil di PanTiltController, maka perlu public dan static
public static void Max485Direction(boolean isTransmit){
if (Max485Direction!=null){
GPIO.SetValue(Max485Direction, isTransmit);
}
}
// dipanggil di PanTiltController, maka perlu public dan static
public static void Blink_LedPanTilt(){
Blink(LedPanTilt);
}
private static void AmplifierControl(boolean isON){
if (AmplifierPower!=null){
GPIO.SetValue(AmplifierPower, isON);
}
}
private static void Blink(RaspberryPi5BPins pin){
if (pin!=null){
if (gpioExecutor!=null){
gpioExecutor.submit(()->{
GPIO.SetValue(pin, true);
try {
Thread.sleep(20);
} catch (InterruptedException ignored) {
}
GPIO.SetValue(pin, false);
});
}
}
} }
private static void init_system_monitoring(){ private static void init_system_monitoring(){
@@ -72,6 +164,41 @@ public class Main {
for(int ii=0;ii<previousCpuInfo.length;ii++){ for(int ii=0;ii<previousCpuInfo.length;ii++){
cpuUsage.put(cpuinfo[ii].name, cpuinfo[ii].cpu_usage(previousCpuInfo[ii])); cpuUsage.put(cpuinfo[ii].name, cpuinfo[ii].cpu_usage(previousCpuInfo[ii]));
} }
previousCpuInfo = cpuinfo;
}
}
NetworkTransmitReceiveInfo[] ntri = SystemInformation.getNetworkTransmitReceiveInfo();
if (ntri.length>0){
if (previousNetworkInfo==null || !Objects.equals(previousNetworkInfo.length, ntri.length)){
previousNetworkInfo = ntri;
} else {
for(int ii=0;ii<previousNetworkInfo.length;ii++){
double txspeed = ntri[ii].TxSpeed(previousNetworkInfo[ii], "B");
String txspeedstr;
if (txspeed < KB_threshold){
txspeedstr = String.format("%.0f B/s", txspeed);
} else if (txspeed < MB_threshold){
txspeedstr = String.format("%.1f KB/s", ntri[ii].TxSpeed(previousNetworkInfo[ii], "KB"));
} else if (txspeed < GB_threshold){
txspeedstr = String.format("%.1f MB/s", ntri[ii].TxSpeed(previousNetworkInfo[ii], "MB"));
} else {
txspeedstr = String.format("%.1f GB/s", ntri[ii].TxSpeed(previousNetworkInfo[ii], "GB"));
}
double rxspeed = ntri[ii].RxSpeed(previousNetworkInfo[ii], "B");
String rxspeedstr;
if (rxspeed < KB_threshold){
rxspeedstr = String.format("%.0f B/s", rxspeed);
} else if (rxspeed < MB_threshold){
rxspeedstr = String.format("%.1f KB/s", ntri[ii].RxSpeed(previousNetworkInfo[ii], "KB"));
} else if (rxspeed < GB_threshold){
rxspeedstr = String.format("%.1f MB/s", ntri[ii].RxSpeed(previousNetworkInfo[ii], "MB"));
} else {
rxspeedstr = String.format("%.1f GB/s", ntri[ii].RxSpeed(previousNetworkInfo[ii], "GB"));
}
networkTX.put(ntri[ii].name, txspeedstr);
networkRX.put(ntri[ii].name, rxspeedstr);
}
previousNetworkInfo = ntri;
} }
} }
} }
@@ -111,6 +238,7 @@ public class Main {
if (IpIsReachable(ip)){ if (IpIsReachable(ip)){
Logger.info("Camera IP is reachable"); Logger.info("Camera IP is reachable");
vapixProtocol = new VapixProtocol(ip, Integer.parseInt(port), username, password); vapixProtocol = new VapixProtocol(ip, Integer.parseInt(port), username, password);
if (vapixProtocol.isSuccessQuery()){
Logger.info("Camera Product Number: "+vapixProtocol.GetProductNumber()); Logger.info("Camera Product Number: "+vapixProtocol.GetProductNumber());
Logger.info("Camera Serial Number: "+vapixProtocol.GetSerialNumber()); Logger.info("Camera Serial Number: "+vapixProtocol.GetSerialNumber());
if (vapixProtocol.PTZEnabled()){ if (vapixProtocol.PTZEnabled()){
@@ -118,6 +246,8 @@ public class Main {
} else Logger.error("PTZ Disabled"); } else Logger.error("PTZ Disabled");
Logger.info("Max Zoom: "+vapixProtocol.GetPTZMaxZoom()); Logger.info("Max Zoom: "+vapixProtocol.GetPTZMaxZoom());
Logger.info("Min Zoom: "+vapixProtocol.GetPTZMinZoom()); Logger.info("Min Zoom: "+vapixProtocol.GetPTZMinZoom());
Blink(LedIpCamera);
} else Logger.error("Failed to query camera");
} else Logger.error("Camera IP is not reachable"); } else Logger.error("Camera IP is not reachable");
} else Logger.error("Camera Password is not valid string"); } else Logger.error("Camera Password is not valid string");
} else Logger.error("Camera Username is not valid string"); } else Logger.error("Camera Username is not valid string");
@@ -205,7 +335,7 @@ public class Main {
@Override @Override
public WebsocketReply onWebsocketCommand(WebsocketCommand command) { public WebsocketReply onWebsocketCommand(WebsocketCommand command) {
Blink(LedWeb);
byte speed; byte speed;
String cmd = command.command.toUpperCase().trim(); String cmd = command.command.toUpperCase().trim();
switch (cmd){ switch (cmd){
@@ -261,6 +391,7 @@ public class Main {
if (afp!=null){ if (afp!=null){
audioFileProperties = afp; audioFileProperties = afp;
audioPlayer.PlayAudioFile(afp, true, pe); audioPlayer.PlayAudioFile(afp, true, pe);
AmplifierControl(true);
return new WebsocketReply("PLAY AUDIO", afp.filename); return new WebsocketReply("PLAY AUDIO", afp.filename);
} else return new WebsocketReply("PLAY AUDIO", "Failed to open audio file "+filename); } else return new WebsocketReply("PLAY AUDIO", "Failed to open audio file "+filename);
@@ -272,15 +403,18 @@ public class Main {
audioPlayer.CloseAudioFile(audioFileProperties); // close previous audio file audioPlayer.CloseAudioFile(audioFileProperties); // close previous audio file
String filename = audioFileProperties.filename; String filename = audioFileProperties.filename;
audioFileProperties = null; audioFileProperties = null;
AmplifierControl(false);
return new WebsocketReply("STOP AUDIO", filename); return new WebsocketReply("STOP AUDIO", filename);
} else return new WebsocketReply("STOP AUDIO", "No audioFileProperties"); } else return new WebsocketReply("STOP AUDIO", "No audioFileProperties");
// ZOOM Related Commands // ZOOM Related Commands
case "GET MAX ZOOM": case "GET MAX ZOOM":
if (vapixProtocol!=null){ if (vapixProtocol!=null){
Blink(LedIpCamera);
return new WebsocketReply("GET MAX ZOOM", String.valueOf(vapixProtocol.GetPTZMaxZoom())); return new WebsocketReply("GET MAX ZOOM", String.valueOf(vapixProtocol.GetPTZMaxZoom()));
} else return new WebsocketReply("GET MAX ZOOM", "VapixProtocol not initialized"); } else return new WebsocketReply("GET MAX ZOOM", "VapixProtocol not initialized");
case "GET ZOOM": case "GET ZOOM":
if (vapixProtocol!=null){ if (vapixProtocol!=null){
Blink(LedIpCamera);
return new WebsocketReply("GET ZOOM", String.valueOf(vapixProtocol.GetCurrentZoomValue())); return new WebsocketReply("GET ZOOM", String.valueOf(vapixProtocol.GetCurrentZoomValue()));
} else return new WebsocketReply("GET ZOOM", "VapixProtocol not initialized"); } else return new WebsocketReply("GET ZOOM", "VapixProtocol not initialized");
case "SET ZOOM": case "SET ZOOM":
@@ -290,6 +424,7 @@ public class Main {
if (zoom<vapixProtocol.GetPTZMinZoom()) zoom = vapixProtocol.GetPTZMinZoom(); if (zoom<vapixProtocol.GetPTZMinZoom()) zoom = vapixProtocol.GetPTZMinZoom();
if (zoom>vapixProtocol.GetPTZMaxZoom()) zoom = vapixProtocol.GetPTZMaxZoom(); if (zoom>vapixProtocol.GetPTZMaxZoom()) zoom = vapixProtocol.GetPTZMaxZoom();
if (vapixProtocol.Zoom(1, zoom)){ if (vapixProtocol.Zoom(1, zoom)){
Blink(LedIpCamera);
return new WebsocketReply("ZOOM", String.valueOf(zoom)); return new WebsocketReply("ZOOM", String.valueOf(zoom));
} else return new WebsocketReply("ZOOM", "Failed to zoom"); } else return new WebsocketReply("ZOOM", "Failed to zoom");
} else return new WebsocketReply("ZOOM", "Invalid Zoom Value"); } else return new WebsocketReply("ZOOM", "Invalid Zoom Value");
@@ -305,6 +440,7 @@ public class Main {
case "GET RESOLUTION": case "GET RESOLUTION":
if (vapixProtocol!=null){ if (vapixProtocol!=null){
int[] res = vapixProtocol.GetCurrentResolution(1); int[] res = vapixProtocol.GetCurrentResolution(1);
Blink(LedIpCamera);
return new WebsocketReply("GET RESOLUTION", String.format("%dx%d", res[0], res[1])); return new WebsocketReply("GET RESOLUTION", String.format("%dx%d", res[0], res[1]));
} else return new WebsocketReply("GET RESOLUTION", "VapixProtocol not initialized"); } else return new WebsocketReply("GET RESOLUTION", "VapixProtocol not initialized");
case "GET AUDIOFILES": case "GET AUDIOFILES":
@@ -326,6 +462,18 @@ public class Main {
data.addProperty(key, String.valueOf(cpuUsage.get(key))); data.addProperty(key, String.valueOf(cpuUsage.get(key)));
} }
} }
if (!networkTX.isEmpty()) {
for(String key : networkTX.keySet()){
if (key.equals("lo")) continue;
data.addProperty(key+"_TX", networkTX.get(key));
}
}
if (!networkRX.isEmpty()) {
for(String key : networkRX.keySet()){
if (key.equals("lo")) continue;
data.addProperty(key+"_RX", networkRX.get(key));
}
}
return new WebsocketReply("GET SYSTEM INFO", data.toString()); return new WebsocketReply("GET SYSTEM INFO", data.toString());
default: default:
return new WebsocketReply("UNKNOWN COMMAND", command.command); return new WebsocketReply("UNKNOWN COMMAND", command.command);