diff --git a/Config/config.properties b/Config/config.properties index e3fa1bc..da0b788 100644 --- a/Config/config.properties +++ b/Config/config.properties @@ -10,7 +10,7 @@ AudioFile03 = pinkNoiseWav.wav AudioFile04 = 04.mp3 AudioFile05 = 05.mp3 AudioVolumeOutput = 100 -SerialPort = /dev/ttyUSB0 +SerialPort = /dev/ttyAMA0 SerialBaudRate = 9600 PanTiltID = 1 WebUsername = admin diff --git a/Html/html/index.html b/Html/html/index.html index 1dfd2f5..1671af3 100644 --- a/Html/html/index.html +++ b/Html/html/index.html @@ -40,23 +40,7 @@ - - - - - - - - - - - - - - - - - +
@@ -66,13 +50,43 @@
-
+

No Status

-
-
- - +
+ +
+
+
+ +

100 %

+
+
+ +
+
+ +

55 °C

+
+
+
+
+ +

15 %

+
+
+ +
+
+
+ + + + +
+ + +
diff --git a/Html/html/index.js b/Html/html/index.js index cb7d47f..6d07cd0 100644 --- a/Html/html/index.js +++ b/Html/html/index.js @@ -14,10 +14,32 @@ let camerastream; -document.addEventListener("DOMContentLoaded", function(){ - camerastream = document.getElementById("camerastream"); +let files = [null,null,null,null,null]; - setInterval(function (){ +let getbase64handle; +let getsysteminfohandle; + +window.addEventListener("unload",function (){ + clearInterval(getbase64handle); + clearInterval(getsysteminfohandle); + if (ws.readyState === WebSocket.OPEN){ + ws.send(JSON.stringify({ + command: "STOP AUDIO", + data: 0 + })); + + // wait a while + const start = Date.now(); + while(Date.now()-start<200){} + } + + ws.close(); +}) + +window.addEventListener("load", function (){ + camerastream = document.getElementById("camerastream"); + // Update camera stream every 50ms / 20fps + getbase64handle = setInterval(function (){ if (ws.readyState === WebSocket.OPEN){ ws.send(JSON.stringify({ command: "GET BASE64", @@ -25,7 +47,16 @@ document.addEventListener("DOMContentLoaded", function(){ })); } else update_camerastream(null); - },10); + },1000/15); + + getsysteminfohandle = setInterval(function (){ + if (ws.readyState === WebSocket.OPEN){ + ws.send(JSON.stringify({ + command: "GET SYSTEM INFO", + data: 0 + })); + } + }, 5000); ws = new WebSocket("/ws"); @@ -49,6 +80,10 @@ document.addEventListener("DOMContentLoaded", function(){ command: "GET RESOLUTION", data: 0 })); + ws.send(JSON.stringify({ + command: "GET AUDIOFILES", + data: 0 + })) } ws.onmessage = function(event){ @@ -63,7 +98,10 @@ document.addEventListener("DOMContentLoaded", function(){ update_camerastream(dx.data); } else update_camerastream(null); if (dx.additional && dx.additional.length>0){ - $('#streaming_status').html(dx.additional); + 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": @@ -75,15 +113,13 @@ document.addEventListener("DOMContentLoaded", function(){ break; case "GET MAX ZOOM": console.log("Get Max Zoom: "+dx.data); - $('#zoom') - .attr("max", dx.data) - .attr("min", 1); - - + 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); - $('#zoom').val(dx.data); break; case "GET RESOLUTION": console.log("Get Resolution: "+dx.data); @@ -114,11 +150,60 @@ document.addEventListener("DOMContentLoaded", function(){ break; case "STOP AUDIO": console.log("Stop Audio"); - $('#status_player').html("Stop Playback"); + 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; } @@ -132,7 +217,9 @@ document.addEventListener("DOMContentLoaded", function(){ set_pan_speed(32); set_tilt_speed(32); -}); +}) + + /** * Update camera stream @@ -176,7 +263,9 @@ function show_stop_and_hide_play(index){ } function allplay(){ - for (let i = 0; i < 5; i++) play_on(i+1); + for (let i = 0; i < 5; i++) { + if (files[i]) play_on(i+1); + } } function play_button(index){ diff --git a/Html/html/setting.html b/Html/html/setting.html index 6af2c57..67f3500 100644 --- a/Html/html/setting.html +++ b/Html/html/setting.html @@ -48,74 +48,72 @@
Audio Files
-
- -
-
-

Preset 1

-
-
- -
+ +
+
+

Preset 1

- -
-
-

Preset 2

-
-
- -
+
+
- -
-
-

Preset 3

-
-
- -
+
+ +
+
+

Preset 2

- -
-
-

Preset 4

-
-
- -
+
+
- -
-
-

Preset 5

-
-
- -
+
+ +
+
+

Preset 3

-
-
-
- -
+
+
- +
+ +
+
+

Preset 4

+
+
+ +
+
+ +
+
+

Preset 5

+
+
+ +
+
+
+
+
+ +
+
@@ -130,7 +128,7 @@

Upload audio files

- +
@@ -146,53 +144,51 @@
Camera
-
-
-
-

IP Address

-
-
- -
+
+
+

IP Address

-
-
-

Port

-
-
- -
+
+
+
+
+
+

Port

+
+
+ +
+
-
-
-

Username

-
-
- -
+
+
+

Username

+
+ +
+
-
-
-

Password

-
-
-
- - +
+
+

Password

+
+
+
+ + -
-
-
-
- -
+
+
+
+
+
- +
@@ -201,48 +197,46 @@
Login
-
-
-
-

Username

-
-
- -
+
+
+

Username

-
-
-

Password

-
-
-
- - +
+ +
+
+
+
+

Password

+
+
+
+ + -
-
-
-

Confirm Password

-
-
-
- - +
+
+
+

Confirm Password

+
+
+
+ + -
-
-
-
- -
+
+
+
+
+
- +
diff --git a/Html/html/setting.js b/Html/html/setting.js index 7074c4a..9cefc79 100644 --- a/Html/html/setting.js +++ b/Html/html/setting.js @@ -54,7 +54,14 @@ function fill_select(index, values){ */ let preset = document.getElementById("preset"+index); preset.innerHTML = ""; + if (values!=null && values.length>0){ + // add empty option + let option = document.createElement("option"); + option.value = ""; + option.innerText = ""; + preset.appendChild(option); + for (let i = 0; i < values.length; i++) { const element = values[i]; let option = document.createElement("option"); @@ -108,4 +115,91 @@ function showConfirm() { icon.classList.remove('fa-eye-slash'); icon.classList.add('fa-eye'); } +} + +function save_audio(){ + let preset1 = $('#preset1').val(); + let preset2 = $('#preset2').val(); + let preset3 = $('#preset3').val(); + let preset4 = $('#preset4').val(); + let preset5 = $('#preset5').val(); + + if (confirm("Are you sure want to change Audio Preset ?")){ + fetch("/setting/audiofile", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: new URLSearchParams({preset1: preset1, preset2: preset2, preset3: preset3, preset4: preset4, preset5: preset5}) + }).then(resp => { + if (resp.status === 200) { + alert("Success"); + } else { + resp.text().then(text=>alert("Failed to change Audio Preset : "+text)); + } + }); + } +} + +function save_camera(){ + let ip = $('#setting_ip').val(); + let port = $('#setting_port').val(); + let username = $('#setting_username').val(); + let password = $('#setting_password').val(); + + if (confirm("Are you sure want to change Camera Information ?")){ + fetch("/setting/camera", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: new URLSearchParams({ip: ip, port: port, username: username, password: password}) + }).then(resp => { + if (resp.status === 200) { + alert("Success"); + } else { + resp.text().then(text => { + alert("Failed to change Camera Information : "+text); + }); + + } + + }); + } +} + +function save_login(){ + let username = $('#login_username').val(); + let password = $('#edit_password').val(); + let confirmpassword = $('#confirm_password').val(); + if (username.length === 0){ + alert("Username cannot be empty"); + return; + } + if (password.length === 0){ + alert("Password cannot be empty"); + return; + } + if (password !== confirmpassword){ + alert("Password not match"); + return; + } + + if (confirm("Are you sure want to change Web Login Information ?")){ + fetch("/setting/weblogin", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: new URLSearchParams({username: username, password: password}) + }).then(resp => { + if (resp.status === 200) { + alert("Success"); + } else { + resp.text().then(text => { + alert("Failed to change Web Login Information : "+text); + }); + } + }) + } } \ No newline at end of file diff --git a/config.properties b/config.properties index 5ad7260..8a5537c 100644 --- a/config.properties +++ b/config.properties @@ -1,9 +1,9 @@ -#Fri Nov 08 16:52:01 WIB 2024 +#Mon Nov 11 16:04:42 WIB 2024 AudioFile01=elangWav.wav AudioFile02=gunshotsWav.wav AudioFile03=pinkNoiseWav.wav -AudioFile04=04.mp3 -AudioFile05=05.mp3 +AudioFile04= +AudioFile05=null AudioVolumeOutput=100 Camera_Rtsp_path=/axis-media/media.amp Camera_ip=192.168.10.17 @@ -12,13 +12,8 @@ Camera_port=80 Camera_user=root PanTiltID=1 SerialBaudRate=9600 -SerialPort=/dev/ttyUSB0 +SerialPort=/dev/ttyAMA0 WebHost=0.0.0.0 WebPassword=bandara WebPort=8080 WebUsername=admin -audiofile1= -audiofile2= -audiofile3= -audiofile4= -audiofile5= diff --git a/pom.xml b/pom.xml index 3437fab..6ca52ab 100644 --- a/pom.xml +++ b/pom.xml @@ -8,11 +8,188 @@ BirdStrikeSoetta 1.0-SNAPSHOT + org.bytedeco javacv-platform - 1.5.10 + 1.5.8 + + + org.bytedeco + leptonica + + + org.bytedeco + leptonica-platform + + + org.bytedeco + artoolkitplus + + + org.bytedeco + artoolkitplus-platform + + + org.bytedeco + libdc1394 + + + org.bytedeco + libdc1394-platform + + + org.bytedeco + libfreenect + + + org.bytedeco + libfreenect-platform + + + org.bytedeco + libfreenect2 + + + org.bytedeco + libfreenect2-platform + + + org.bytedeco + flycapture + + + org.bytedeco + flycapture-platform + + + org.bytedeco + librealsense + + + org.bytedeco + librealsense-platform + + + org.bytedeco + librealsense2 + + + org.bytedeco + librealsense2-platform + + + org.bytedeco + videoinput + + + org.bytedeco + videoinput-platform + + + org.bytedeco + tesseract + + + org.bytedeco + tesseract-platform + + + + org.bytedeco + javacpp-platform + + + org.bytedeco + openblas-platform + + + org.bytedeco + opencv-platform + + + org.bytedeco + ffmpeg-platform + + + + + org.bytedeco + opencv + 4.6.0-1.5.8 + windows-x86_64 + + + org.bytedeco + javacpp + 1.5.8 + windows-x86_64 + + + org.bytedeco + openblas + 0.3.21-1.5.8 + windows-x86_64 + + + org.bytedeco + ffmpeg + 5.1.2-1.5.8 + windows-x86_64 + + + + org.bytedeco + opencv + 4.6.0-1.5.8 + linux-arm64 + + + org.bytedeco + javacpp + 1.5.8 + linux-arm64 + + + org.bytedeco + openblas + 0.3.21-1.5.8 + linux-arm64 + + + org.bytedeco + ffmpeg + 5.1.2-1.5.8 + linux-arm64 + + + io.javalin javalin @@ -52,6 +229,11 @@ jackson-databind 2.17.2 + + com.google.code.gson + gson + 2.11.0 + @@ -60,4 +242,13 @@ UTF-8 + + + + kr.motd.maven + os-maven-plugin + 1.7.0 + + + \ No newline at end of file diff --git a/src/main/java/Audio/AudioPlayer.java b/src/main/java/Audio/AudioPlayer.java index a0b29d9..8056569 100644 --- a/src/main/java/Audio/AudioPlayer.java +++ b/src/main/java/Audio/AudioPlayer.java @@ -1,14 +1,15 @@ package Audio; +import lombok.Getter; import org.tinylog.Logger; import java.io.File; public class AudioPlayer { - Bass bass; - int deviceid = -1; - boolean inited = false; + private final Bass bass; + private int deviceid = -1; + @Getter private boolean inited = false; int playbackhandle = 0; float playbackvolume = 1.0f; @@ -48,13 +49,49 @@ public class AudioPlayer { } } + /** + * Get BASS_DEVICEINFO for a device + * @param device device id + * @return BASS_DEVICEINFO object or null if failed + */ + public Bass.BASS_DEVICEINFO GetDeviceInfo(int device){ + Bass.BASS_DEVICEINFO info = new Bass.BASS_DEVICEINFO(); + if (bass.BASS_GetDeviceInfo(device, info)){ + return info; + } + return null; + } + + /** + * Find Device ID with Name + * @param name device name + * @return device id, or -1 if not found + */ + public int FindDeviceIDWithName(String name){ + int result = -1; + int ii = 1; + while(true){ + Bass.BASS_DEVICEINFO info = new Bass.BASS_DEVICEINFO(); + if (bass.BASS_GetDeviceInfo(ii, info)){ + if (info.name.contains(name)){ + result = ii; + break; + } + ii++; + } else { + // gak ada lagi + break; + } + } + return result; + } + /** * Open Output Device * @param device device id, starts from 1 * @param freq output frequency * @return true if success */ - @SuppressWarnings("UnusedReturnValue") public boolean OpenDevice(int device, int freq){ int flag = Bass.BASS_DEVICE_REINIT | Bass.BASS_DEVICE_16BITS | Bass.BASS_DEVICE_MONO | Bass.BASS_DEVICE_FREQ; boolean success = bass.BASS_Init(device, freq, flag); @@ -69,7 +106,8 @@ public class AudioPlayer { } /** - * Set Master Volume + * Set Master Output Volume + * Master Output Volume related to the system volume (Alsamixer on Linux, Volume Mixer on Windows) * @param value volume value, 0-100 */ public void setMasterVolume(int value){ @@ -84,7 +122,8 @@ public class AudioPlayer { } /** - * Get Master Volume + * Get Master Output Volume + * Master Output Volume related to the system volume (Alsamixer on Linux, Volume Mixer on Windows) * @return 0 - 100, or -1 if failed */ @SuppressWarnings("unused") @@ -99,6 +138,11 @@ public class AudioPlayer { return -1; } + /** + * Get Playback Volume + * is the volume of the currently playing audio file + * @return 0 - 100 + */ public int getPlaybackvolume(){ if (playbackvolume<0) return 0; @@ -108,6 +152,11 @@ public class AudioPlayer { return (int)(playbackvolume*100); } + /** + * Set Playback Volume + * is the volume of the currently playing audio file + * @param value 0 - 100 + */ public void setPlaybackvolume(int value){ if (value<0) value = 0; if (value>100) value = 100; @@ -119,11 +168,17 @@ public class AudioPlayer { private float lastplaybackvolume = 1.0f; + /** + * Set Playback Volume to 0 + */ public void Mute(){ lastplaybackvolume = playbackvolume; setPlaybackvolume(0); } + /** + * Set Playback Volume to last volume before Mute + */ public void Unmute(){ setPlaybackvolume((int)lastplaybackvolume*100); } @@ -164,6 +219,12 @@ public class AudioPlayer { } } + /** + * Play Audio File + * @param prop AudioFileProperties object to play + * @param looping set true to loop the audio file + * @param event PlaybackEvent object to handle playback event + */ public void PlayAudioFile(AudioFileProperties prop, boolean looping, PlaybackEvent event){ if (inited){ if (prop!=null){ diff --git a/src/main/java/Camera/GrabbingTask.java b/src/main/java/Camera/GrabbingTask.java index 9e02a89..c8dcedc 100644 --- a/src/main/java/Camera/GrabbingTask.java +++ b/src/main/java/Camera/GrabbingTask.java @@ -9,6 +9,7 @@ import lombok.Getter; import lombok.Setter; import org.bytedeco.javacv.Frame; import org.bytedeco.javacv.FrameGrabber; +import org.tinylog.Logger; public class GrabbingTask implements Runnable { @Setter private Consumer onMessageUpdate; @@ -16,13 +17,13 @@ public class GrabbingTask implements Runnable { @Setter private Consumer onLQFrameUpdate; @Setter private Consumer onHQBase64Update; @Setter private Consumer onLQBase64Update; + @Getter private int CaptureFPS = 0; private final AtomicBoolean isGrabbing; private final FrameGrabber grabber; @Getter private final int lowquality_width = 640; @Getter private final int lowquality_height = 360; - private void updateMessage(String message) { if (onMessageUpdate != null) { onMessageUpdate.accept(message); @@ -68,27 +69,44 @@ public class GrabbingTask implements Runnable { } + public void Stop(){ + isGrabbing.set(false); + } + private void grabprocess() throws Exception{ + grabber.flush(); + Frame fr =grabber.grab(); + + if (fr!=null){ + updateHQFrame(fr); + updateHQBase64(SomeCodes.FrameToBase64(fr)); + Frame resized = SomeCodes.ResizeFrame(fr, lowquality_width, lowquality_height); + updateLQFrame(resized); + updateLQBase64(SomeCodes.FrameToBase64(resized)); + } else updateMessage("Grabber returned null frame"); + } @Override public void run() { isGrabbing.set(true); + Logger.info("Grabbing Task started"); + double fps = grabber.getFrameRate(); + Logger.info("Grabber framerate = {}", fps); + long starttick = System.currentTimeMillis(); while (isGrabbing.get()) { - try { - Frame fr =grabber.grab(); - if (fr!=null){ - updateHQFrame(fr); - updateHQBase64(SomeCodes.FrameToBase64(fr)); - Frame resized = SomeCodes.ResizeFrame(fr, lowquality_width, lowquality_height); - updateLQFrame(resized); - updateLQBase64(SomeCodes.FrameToBase64(resized)); - } else updateMessage("Grabber returned null frame"); - } catch (Exception e) { - updateMessage("Error grabbing frame: " + e.getMessage()); + + long elapsed = System.currentTimeMillis() - starttick; + starttick = System.currentTimeMillis(); + //Logger.info("Elapsed time = {} ms", elapsed); + if (elapsed>0) CaptureFPS = (int) (1000 / elapsed); + try{ + Thread.yield(); + grabprocess(); + } catch (Exception e){ + Logger.error("Error grabbing frame: "+e.getMessage()); } } - - + Logger.info("Grabbing Task stopped"); } } diff --git a/src/main/java/Camera/PanTiltController.java b/src/main/java/Camera/PanTiltController.java index cc0582b..0170344 100644 --- a/src/main/java/Camera/PanTiltController.java +++ b/src/main/java/Camera/PanTiltController.java @@ -3,13 +3,14 @@ package Camera; import com.fazecast.jSerialComm.SerialPort; import org.tinylog.Logger; + /** * Pan Tilt Controller * Using PelcoD protocol - * Source : https://www.commfront.com/pages/pelco-d-protocol-tutorial + * Source : PelcoD Tutorial */ public class PanTiltController { - private final SerialPort serialPort; + private SerialPort serialPort; private final byte cameraid; /** * Open Pan Tilt Controller @@ -17,21 +18,38 @@ public class PanTiltController { * @param baudrate baudrate used */ public PanTiltController(String portname, int baudrate, int cameraid){ - serialPort = SerialPort.getCommPort(portname); - serialPort.setBaudRate(baudrate); this.cameraid = (byte)cameraid; - if (serialPort.openPort()){ - Logger.info("Serial Port {} opened successfully at {}", portname, baudrate); - } else { - Logger.info("Failed to open Serial Port {} at {}", portname, baudrate); - } + SerialPort[] comports = SerialPort.getCommPorts(); + if (comports.length>0){ + for (SerialPort port : comports){ + Logger.info("Available Serial Port : {}", port.getSystemPortName()); + if (port.getSystemPortName().equals(portname)){ + Logger.info("Serial Port {} found", portname); + serialPort = port; + + break; + } + } + if (serialPort!=null){ + serialPort.setBaudRate(baudrate); + if (serialPort.openPort()){ + Logger.info("Serial Port {} opened successfully at {}", portname, baudrate); + } else { + Logger.info("Failed to open Serial Port {} at {}", portname, baudrate); + } + } else Logger.info("Serial Port {} not found", portname); + } else Logger.info("No Serial Port found"); + + + + } /** * Close Pan Tilt Controller */ public void Close(){ - serialPort.closePort(); + if (serialPort!=null) serialPort.closePort(); Logger.info("Serial Port closed"); } diff --git a/src/main/java/Camera/RtspGrabber.java b/src/main/java/Camera/RtspGrabber.java index 5be2449..c686b6d 100644 --- a/src/main/java/Camera/RtspGrabber.java +++ b/src/main/java/Camera/RtspGrabber.java @@ -7,24 +7,59 @@ import org.tinylog.Logger; import java.util.concurrent.atomic.AtomicBoolean; +import static Other.SomeCodes.gson; + public class RtspGrabber { private final String rtspUrl; private FFmpegFrameGrabber grabber; private final AtomicBoolean isGrabbing = new AtomicBoolean(false); - private @Getter Frame lastHQFrame = null; - private @Getter Frame lastLQFrame = null; - private @Getter String lastHQBase64 = null; - private @Getter String lastLQBase64 = null; + private Frame lastHQFrame = null; + private Frame lastLQFrame = null; + private String lastHQBase64 = null; + private String lastLQBase64 = null; private @Getter int HQWidth = 0; private @Getter int HQHeight = 0; private @Getter int LQWidth = 0; private @Getter int LQHeight = 0; + private GrabbingTask grabbingTask; public RtspGrabber(String ip, String path) { this.rtspUrl = "rtsp://" + ip + path; Logger.info("RtspGrabber created with url: " + rtspUrl); } + private synchronized void setLastHQFrame(Frame frame){ + lastHQFrame = frame; + } + + public synchronized Frame getLastHQFrame(){ + return lastHQFrame; + } + + private synchronized void setLastLQFrame(Frame frame){ + lastLQFrame = frame; + } + + public synchronized Frame getLastLQFrame(){ + return lastLQFrame; + } + + private synchronized void setLastHQBase64(String base64){ + lastHQBase64 = base64; + } + + public synchronized String getLastHQBase64(){ + return lastHQBase64; + } + + private synchronized void setLastLQBase64(String base64){ + lastLQBase64 = base64; + } + + public synchronized String getLastLQBase64(){ + return lastLQBase64; + } + /** * Start grabbing frames from rtsp * @param useTcp Use tcp instead of udp @@ -34,11 +69,12 @@ public class RtspGrabber { try{ grabber = FFmpegFrameGrabber.createDefault(rtspUrl); if (useTcp) grabber.setOption("rtsp_transport", "tcp"); - + grabber.setTimeout(2000); grabber.setImageWidth(width); grabber.setImageHeight(height); grabber.setPixelFormat(avutil.AV_PIX_FMT_BGR24); grabber.start(); + avutil.av_log_set_level(avutil.AV_LOG_ERROR); @@ -49,7 +85,7 @@ public class RtspGrabber { tt.setOnHQFrameUpdate(value -> { if (value!=null){ if (value.imageWidth>0 && value.imageHeight>0){ - lastHQFrame = value; + setLastHQFrame(value); HQWidth = value.imageWidth; HQHeight = value.imageHeight; } @@ -59,14 +95,15 @@ public class RtspGrabber { tt.setOnLQFrameUpdate(value -> { if (value!=null){ if (value.imageWidth>0 && value.imageHeight>0){ - lastLQFrame = value; + setLastLQFrame(value); LQWidth = value.imageWidth; LQHeight = value.imageHeight; } } }); - tt.setOnHQBase64Update(value -> lastHQBase64 = value); - tt.setOnLQBase64Update(value -> lastLQBase64 = value); + tt.setOnHQBase64Update(this::setLastHQBase64); + tt.setOnLQBase64Update(this::setLastLQBase64); + grabbingTask = tt; new Thread(tt).start(); } catch (Exception e){ @@ -78,17 +115,28 @@ public class RtspGrabber { * Stop grabbing frames */ public void Stop(){ + isGrabbing.set(false); + if (grabbingTask!=null){ + grabbingTask.Stop(); + } + grabbingTask = null; if (grabber!=null) { try{ - isGrabbing.set(false); grabber.stop(); + grabber.releaseUnsafe(); Logger.info("Grabber stopped"); } catch (Exception e){ Logger.error("Error stopping grabber: " + e.getMessage()); } } + grabber = null; } + public String LQStreamingStatus(){ + return gson.toJson(new String[]{String.valueOf(LQWidth), String.valueOf(LQHeight) , String.valueOf(grabbingTask.getCaptureFPS())}); + } - + public String HQStreamingStatus(){ + return gson.toJson(new String[]{String.valueOf(HQWidth), String.valueOf(HQHeight) , String.valueOf(grabbingTask.getCaptureFPS())}); + } } diff --git a/src/main/java/Other/SomeCodes.java b/src/main/java/Other/SomeCodes.java index 3e01d97..cb573fd 100644 --- a/src/main/java/Other/SomeCodes.java +++ b/src/main/java/Other/SomeCodes.java @@ -1,5 +1,6 @@ package Other; +import com.google.gson.Gson; import org.bytedeco.javacpp.Loader; import org.bytedeco.javacv.Frame; import org.bytedeco.javacv.Java2DFrameConverter; @@ -42,6 +43,7 @@ public class SomeCodes { public static final boolean haveOpenCL = opencv_core.haveOpenCL(); public static boolean useOpenCL; private static final Base64.Encoder base64encoder = java.util.Base64.getEncoder(); + public static final Gson gson = new Gson(); public static String[] GetAudioFiles(){ try{ @@ -49,6 +51,7 @@ public class SomeCodes { } catch (Exception e){ Logger.error("Error getting audio files: "+e.getMessage()); } + return new String[0]; } @@ -199,10 +202,15 @@ public class SomeCodes { } - + /** + * Load properties file + * @param filename properties file name + * @return Properties object loaded, or empty new Properties object if error + */ public static @NotNull Properties LoadProperties(String filename){ try{ - InputStream is = new FileInputStream(filename); + File ff = new File(currentDirectory, filename); + InputStream is = new FileInputStream(ff); Properties prop = new Properties(); prop.load(is); return prop; @@ -212,9 +220,16 @@ public class SomeCodes { return new Properties(); } + /** + * Save properties file + * @param prop Properties object to save + * @param filename properties file name + * @return true if success, false otherwise + */ public static boolean SaveProperties(Properties prop, String filename){ try{ - OutputStream os = new FileOutputStream(filename); + File ff = new File(currentDirectory, filename); + OutputStream os = new FileOutputStream(ff); prop.store(os, null); return true; } catch (Exception e){ @@ -236,6 +251,7 @@ public class SomeCodes { UMat src = new UMat(); source.copyTo(src); UMat dst = new UMat(); + opencv_imgproc.resize(src, dst, sz); dst.copyTo(dest); } else { @@ -249,4 +265,19 @@ public class SomeCodes { Mat resized = ResizeMat(mat, width, height); return matConverter.convert(resized); } + + /** + * check if an ip address is reachable + * @param ipaddress ip address to check + * @return true if valid and reachable, false otherwise + */ + public static boolean IpIsReachable(String ipaddress){ + try{ + InetAddress inet = InetAddress.getByName(ipaddress); + return inet.isReachable(1000); + } catch (Exception e){ + Logger.error("Error checking ip address: "+e.getMessage()); + } + return false; + } } diff --git a/src/main/java/SBC/CpuInfo.java b/src/main/java/SBC/CpuInfo.java new file mode 100644 index 0000000..f7bff26 --- /dev/null +++ b/src/main/java/SBC/CpuInfo.java @@ -0,0 +1,8 @@ +package SBC; + +public class CpuInfo { + public int processorCount; + public String revision; + public String serial; + public String model; +} diff --git a/src/main/java/SBC/GPIO.java b/src/main/java/SBC/GPIO.java index 0d5f80e..1deadd4 100644 --- a/src/main/java/SBC/GPIO.java +++ b/src/main/java/SBC/GPIO.java @@ -6,7 +6,6 @@ import org.tinylog.Logger; import java.nio.file.Files; import java.nio.file.Path; -@SuppressWarnings("unused") public class GPIO { private static final Path gpioPath = Path.of("/sys/class/gpio"); @@ -14,19 +13,15 @@ public class GPIO { private static final Path gpioUnexportPath = Path.of("/sys/class/gpio/unexport"); - public static boolean IsRaspberry64(){ + public static boolean HaveGPIO(){ if (Platform.isLinux()){ - if (Platform.isARM()){ - if (Platform.is64Bit()){ - if (gpioPath.toFile().isDirectory()){ - if (gpioExportPath.toFile().isFile()){ - if (gpioUnexportPath.toFile().isFile()){ - return true; - } else Logger.error("GPIO unexport path is not found"); - } else Logger.error("GPIO export path is not found"); - } else Logger.error("GPIO path is not found"); - } else Logger.info("Device is not 64 bit"); - } else Logger.info("Device is not ARM"); + if (gpioPath.toFile().isDirectory()){ + if (gpioExportPath.toFile().isFile()){ + if (gpioUnexportPath.toFile().isFile()){ + return true; + } else Logger.error("GPIO unexport path is not found"); + } else Logger.error("GPIO export path is not found"); + } else Logger.error("GPIO path is not found"); } else Logger.info("OS is not Linux"); return false; } diff --git a/src/main/java/SBC/NetworkTransmitReceiveInfo.java b/src/main/java/SBC/NetworkTransmitReceiveInfo.java new file mode 100644 index 0000000..03d5917 --- /dev/null +++ b/src/main/java/SBC/NetworkTransmitReceiveInfo.java @@ -0,0 +1,84 @@ +package SBC; + +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +import static Other.SomeCodes.ValidString; + +public class NetworkTransmitReceiveInfo { + public String name; + public long bytesReceived; + public long packetsReceived; + public long errorsReceived; + public long droppedReceived; + public long fifoReceived; + public long frameReceived; + public long compressedReceived; + public long multicastReceived; + public long bytesTransmitted; + public long packetsTransmitted; + public long errorsTransmitted; + public long droppedTransmitted; + public long fifoTransmitted; + public long collsTransmitted; + public long carrierTransmitted; + public long compressedTransmitted; + public long timetick; + + /** + * Calculate the download speed + * @param prev Previous NetworkTransmitReceiveInfo + * @param unit Speed unit (KB, MB, GB) + * @return Download speed in {unit}/second + */ + public double RxSpeed(NetworkTransmitReceiveInfo prev, String unit){ + if (prev!=null){ + if (Objects.equals(prev.name, name)){ + long timeDiff = timetick - prev.timetick; + if (timeDiff>0){ + long bytesDiff = bytesReceived - prev.bytesReceived; + if (ValidString(unit)) unit = unit.toUpperCase(); + Double speed = ConvertToUnit(unit, timeDiff, bytesDiff); + if (speed != null) return speed; + } + } + } + return 0; + } + + @Nullable + private Double ConvertToUnit(String unit, long timeDiff, long bytesDiff) { + if (bytesDiff>0){ + double speed = ((double) bytesDiff / timeDiff) * 1000; + return switch (unit) { + case "KB" -> speed / 1024; + case "MB" -> speed / 1024 / 1024; + case "GB" -> speed / 1024 / 1024 / 1024; + default -> speed; + }; + } + return null; + } + + /** + * Calculate the upload speed + * @param prev Previous NetworkTransmitReceiveInfo + * @param unit Speed unit (KB, MB, GB) + * @return Upload speed in {unit}/second + */ + public double TxSpeed(NetworkTransmitReceiveInfo prev, String unit){ + if (prev!=null){ + if (Objects.equals(prev.name, name)){ + long timeDiff = timetick - prev.timetick; + if (timeDiff>0){ + long bytesDiff = bytesTransmitted - prev.bytesTransmitted; + if (ValidString(unit)) unit = unit.toUpperCase(); + Double speed = ConvertToUnit(unit, timeDiff, bytesDiff); + if (speed != null) return speed; + } + } + } + return 0; + } +} diff --git a/src/main/java/SBC/ProcessorStatus.java b/src/main/java/SBC/ProcessorStatus.java new file mode 100644 index 0000000..355c041 --- /dev/null +++ b/src/main/java/SBC/ProcessorStatus.java @@ -0,0 +1,45 @@ +package SBC; + +public class ProcessorStatus { + public String name; + public int user; + public int nice; + public int system; + public int idle; + public int iowait; + public int irq; + public int softirq; + public int steal; + public int guest; + public int guest_nice; + + /** + * Calculate total CPU time + * @return Total CPU time + */ + public int total_time(){ + return user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice; + } + + /** + * Calculate idle CPU time + * @return Idle CPU time + */ + public int idle_time(){ + return idle + iowait; + } + + /** + * Calculate CPU usage percentage + * @param prev Previous CPU information + * @return CPU usage percentage 0 - 100 + */ + public int cpu_usage(ProcessorStatus prev){ + if (prev!=null){ + int total_diff = total_time() - prev.total_time(); + int idle_diff = idle_time() - prev.idle_time(); + return (int)(100.0 * (total_diff - idle_diff) / total_diff); + } + return 0; + } +} diff --git a/src/main/java/SBC/RamInformation.java b/src/main/java/SBC/RamInformation.java new file mode 100644 index 0000000..d9fc08c --- /dev/null +++ b/src/main/java/SBC/RamInformation.java @@ -0,0 +1,12 @@ +package SBC; + +public class RamInformation { + public int totalKB; + public int usedKB; + public int availableKB; + public int swapTotalKB; + public int swapFreeKB; + public double RamUsagePercentage(){ + return (double) usedKB / totalKB * 100; + } +} diff --git a/src/main/java/SBC/SystemInformation.java b/src/main/java/SBC/SystemInformation.java new file mode 100644 index 0000000..d5d045f --- /dev/null +++ b/src/main/java/SBC/SystemInformation.java @@ -0,0 +1,179 @@ +package SBC; + +import com.sun.jna.Platform; + +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SystemInformation { + + + + public static int getCPUTemperature() { + if (Platform.isLinux()){ + File ff = new File("/sys/class/thermal/thermal_zone0/temp"); + if (ff.isFile() && ff.canRead()){ + try{ + String value = Files.readString(ff.toPath()).trim(); + return Integer.parseInt(value) / 1000; + } catch (Exception ignored) { + } + } + } + return 0; + } + + public static NetworkTransmitReceiveInfo[] getNetworkTransmitReceiveInfo(){ + if (Platform.isLinux()){ + File ff = new File("/proc/net/dev"); + if (ff.isFile() && ff.canRead()){ + List result = new ArrayList<>(); + final Pattern pattern = Pattern.compile("\\s+(.*):\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"); + try{ + String[] lines = Files.readString(ff.toPath()).split("\n"); + for (String line : lines){ + Matcher m = pattern.matcher(line); + if (m.find()){ + NetworkTransmitReceiveInfo info = new NetworkTransmitReceiveInfo(); + info.name = m.group(1).trim(); + info.bytesReceived = Long.parseLong(m.group(2)); + info.packetsReceived = Long.parseLong(m.group(3)); + info.errorsReceived = Long.parseLong(m.group(4)); + info.droppedReceived = Long.parseLong(m.group(5)); + info.fifoReceived = Long.parseLong(m.group(6)); + info.frameReceived = Long.parseLong(m.group(7)); + info.compressedReceived = Long.parseLong(m.group(8)); + info.multicastReceived = Long.parseLong(m.group(9)); + info.bytesTransmitted = Long.parseLong(m.group(10)); + info.packetsTransmitted = Long.parseLong(m.group(11)); + info.errorsTransmitted = Long.parseLong(m.group(12)); + info.droppedTransmitted = Long.parseLong(m.group(13)); + info.fifoTransmitted = Long.parseLong(m.group(14)); + info.collsTransmitted = Long.parseLong(m.group(15)); + info.carrierTransmitted = Long.parseLong(m.group(16)); + info.compressedTransmitted = Long.parseLong(m.group(17)); + info.timetick = System.currentTimeMillis(); + result.add(info); + } + } + } catch (Exception ignored) { + + } + return result.toArray(new NetworkTransmitReceiveInfo[0]); + } + } + return new NetworkTransmitReceiveInfo[0]; + } + + public static CpuInfo getCPUInfo(){ + CpuInfo result = new CpuInfo(); + if (Platform.isLinux()){ + File ff = new File("/proc/cpuinfo"); + if (ff.isFile() && ff.canRead()){ + final Pattern pattern = Pattern.compile( "\\s*(.*):\\s*(.*)"); + try{ + String[] lines = Files.readString(ff.toPath()).split("\n"); + for (String line : lines){ + Matcher m = pattern.matcher(line); + if (m.find()){ + String key = m.group(1).trim(); + String value = m.group(2).trim(); + switch (key){ + case "processor": + result.processorCount++; + break; + case "Revision": + result.revision = value; + break; + case "Serial": + result.serial = value; + break; + case "Model": + result.model = value; + break; + } + } + } + } catch (Exception ignored) { + } + } + } + return result; + } + + public static ProcessorStatus[] getProcStat(){ + if (Platform.isLinux()){ + File ff = new File("/proc/stat"); + if (ff.isFile() && ff.canRead()){ + final Pattern pattern = Pattern.compile( "(cpu\\d?)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"); + List result = new ArrayList<>(); + try{ + String[] lines = Files.readString(ff.toPath()).split("\n"); + for (String line : lines){ + Matcher m = pattern.matcher(line); + if (m.find()){ + ProcessorStatus info = new ProcessorStatus(); + info.name = m.group(1).trim(); + info.user = Integer.parseInt(m.group(2)); + info.nice = Integer.parseInt(m.group(3)); + info.system = Integer.parseInt(m.group(4)); + info.idle = Integer.parseInt(m.group(5)); + info.iowait = Integer.parseInt(m.group(6)); + info.irq = Integer.parseInt(m.group(7)); + info.softirq = Integer.parseInt(m.group(8)); + info.steal = Integer.parseInt(m.group(9)); + info.guest = Integer.parseInt(m.group(10)); + info.guest_nice = Integer.parseInt(m.group(11)); + result.add(info); + } + } + return result.toArray(new ProcessorStatus[0]); + } catch (Exception ignored) { + } + } + } + return new ProcessorStatus[0]; + } + + public static RamInformation getRAMInformation(){ + + RamInformation result = new RamInformation(); + if (Platform.isLinux()){ + File ff = new File("/proc/meminfo"); + if (ff.isFile() && ff.canRead()){ + final Pattern pattern = Pattern.compile("(.*):\\s+(\\d+).kB"); + try{ + String[] lines = Files.readString(ff.toPath()).split("\n"); + for (String line : lines) { + Matcher m = pattern.matcher(line); + if (m.find()){ + String key = m.group(1); + int value = Integer.parseInt(m.group(2)); + switch (key){ + case "MemTotal": + result.totalKB = value; + break; + case "MemAvailable": + result.availableKB = value; + break; + case "SwapTotal": + result.swapTotalKB = value; + break; + case "SwapFree": + result.swapFreeKB = value; + break; + } + } + } + } catch (Exception ignored) { + } + result.usedKB = result.totalKB - result.availableKB; + } + } + return result; + } +} diff --git a/src/main/java/Web/AudioFilesInfo.java b/src/main/java/Web/AudioFilesInfo.java new file mode 100644 index 0000000..89a1640 --- /dev/null +++ b/src/main/java/Web/AudioFilesInfo.java @@ -0,0 +1,9 @@ +package Web; + +public class AudioFilesInfo { + public String preset1; + public String preset2; + public String preset3; + public String preset4; + public String preset5; +} diff --git a/src/main/java/Web/WebServer.java b/src/main/java/Web/WebServer.java index a263d1d..a147072 100644 --- a/src/main/java/Web/WebServer.java +++ b/src/main/java/Web/WebServer.java @@ -3,12 +3,14 @@ package Web; import Other.SomeCodes; import io.javalin.Javalin; import io.javalin.http.UploadedFile; +import io.javalin.util.FileUtil; import io.javalin.util.JavalinException; import io.javalin.websocket.*; +import lombok.Getter; +import lombok.Setter; import org.tinylog.Logger; -import java.nio.file.Files; -import java.nio.file.Path; +import java.util.List; import java.util.Objects; import java.util.Properties; import java.util.Set; @@ -20,20 +22,24 @@ import static io.javalin.apibuilder.ApiBuilder.*; @SuppressWarnings({"unused"}) public class WebServer { + private @Getter @Setter String webusername; + private @Getter @Setter String webpassword; private final Javalin app; private final Set connectedWebsocketClients = ConcurrentHashMap.newKeySet(); public WebServer(WebsocketEvent event, String webusername, String webpassword){ + this.webusername = webusername; + this.webpassword = webpassword; app = Javalin.create(config -> { config.staticFiles.add("/html"); config.router.apiBuilder(()-> path("setting", () ->{ get(ctx -> ctx.json(SettingInfo.getInstance())); path("audiofile",()-> post(ctx -> { Logger.info("api /setting/audiofile"); - String audiofile1 = ctx.formParam("1"); - String audiofile2 = ctx.formParam("2"); - String audiofile3 = ctx.formParam("3"); - String audiofile4 = ctx.formParam("4"); - String audiofile5 = ctx.formParam("5"); + String audiofile1 = ctx.formParam("preset1"); + String audiofile2 = ctx.formParam("preset2"); + String audiofile3 = ctx.formParam("preset3"); + String audiofile4 = ctx.formParam("preset4"); + String audiofile5 = ctx.formParam("preset5"); Logger.info("audiofile1: {}", audiofile1); Logger.info("audiofile2: {}", audiofile2); Logger.info("audiofile3: {}", audiofile3); @@ -41,41 +47,59 @@ public class WebServer { Logger.info("audiofile5: {}", audiofile5); Properties prop = SomeCodes.LoadProperties("config.properties"); - prop.setProperty("audiofile1", audiofile1!=null?audiofile1:""); - prop.setProperty("audiofile2", audiofile2!=null?audiofile2:""); - prop.setProperty("audiofile3", audiofile3!=null?audiofile3:""); - prop.setProperty("audiofile4", audiofile4!=null?audiofile4:""); - prop.setProperty("audiofile5", audiofile5!=null?audiofile5:""); + prop.setProperty("AudioFile01", audiofile1!=null?audiofile1:""); + prop.setProperty("AudioFile02", audiofile2!=null?audiofile2:""); + prop.setProperty("AudioFile03", audiofile3!=null?audiofile3:""); + prop.setProperty("AudioFile04", audiofile4!=null?audiofile4:""); + prop.setProperty("AudioFile05", audiofile5!=null?audiofile5:""); if (SaveProperties(prop, "config.properties")){ Logger.info("audiofile saved"); ctx.status(200); } else { Logger.error("Failed to save audiofile"); ctx.status(400); + ctx.result("Failed to save audiofile"); } })); path("uploadaudiofile", ()-> post(ctx -> { - UploadedFile file = ctx.uploadedFile("file"); - if (file!=null){ - try { - Path targetsave = audioPath.resolve(file.filename()); - Files.copy(file.content(), targetsave); - Logger.info("Uploaded file: {}, size: {} saved at {}", file.filename(),file.size(), targetsave); - } catch (Exception e){ - Logger.error("Failed to save uploaded file: {}, Message: {}", file.filename(), e.getMessage()); - } + List uploadedFileList = ctx.uploadedFiles(); + int size = uploadedFileList.size(); + if (size>0){ + uploadedFileList.forEach(ff ->{ + String targetsave = audioPath.resolve(ff.filename()).toString(); + FileUtil.streamToFile(ff.content(), targetsave); + Logger.info("Uploaded file: {}", targetsave); + }); + ctx.status(200); + ctx.result("UploadedFiles: "+size); + } else { + ctx.status(400); + ctx.result("No file uploaded"); } + + })); path("weblogin", ()-> post(ctx -> { String username = ctx.formParam("username"); String password = ctx.formParam("password"); + Logger.info("api /setting/weblogin"); + Logger.info("username: {}", username); + Logger.info("password: {}", password); + Properties prop = SomeCodes.LoadProperties("config.properties"); prop.setProperty("WebUsername", ValidString(username)?username:"admin"); prop.setProperty("WebPassword", ValidString(password)?password:"bandara"); if (SaveProperties(prop, "config.properties")){ - ctx.status(200); + Logger.info("weblogin saved"); + + //ctx.status(200); + this.webusername = username; + this.webpassword = password; + ctx.redirect("/logout"); } else { + Logger.error("Failed to save weblogin"); ctx.status(400); + ctx.result("Failed to save weblogin"); } })); path("camera",()-> post(ctx -> { @@ -83,6 +107,11 @@ public class WebServer { String camera_port = ctx.formParam("port"); String camera_username = ctx.formParam("username"); String camera_password = ctx.formParam("password"); + Logger.info("api /setting/camera"); + Logger.info("camera_ip: {}", camera_ip); + Logger.info("camera_port: {}", camera_port); + Logger.info("camera_username: {}", camera_username); + Logger.info("camera_password: {}", camera_password); Properties prop = SomeCodes.LoadProperties("config.properties"); prop.setProperty("Camera_ip", ValidString(camera_ip)?camera_ip:"192.168.0.4"); @@ -90,9 +119,13 @@ public class WebServer { prop.setProperty("Camera_user", ValidString(camera_username)?camera_username:"root"); prop.setProperty("Camera_password", ValidString(camera_password)?camera_password:"password"); if (SaveProperties(prop, "config.properties")){ + Logger.info("IP camera setting saved"); ctx.status(200); + if (event!=null) event.NewCameraConfiguration(); } else { + Logger.error("Failed to save IP camera setting"); ctx.status(400); + ctx.result("Failed to save IP camera setting"); } })); @@ -103,7 +136,7 @@ public class WebServer { if (ctx.sessionAttribute("username")==null) { // belum login ctx.redirect("/login.html"); - } else if (Objects.equals(ctx.sessionAttribute("username"), webusername)){ + } else if (Objects.equals(ctx.sessionAttribute("username"), this.webusername)){ // sudah login ctx.redirect("/index.html"); } else { @@ -127,11 +160,12 @@ public class WebServer { app.post("/login", ctx ->{ String username = ctx.formParam("username"); String password = ctx.formParam("password"); - if (Objects.equals(username, webusername) && Objects.equals(password, webpassword)){ + if (Objects.equals(username, this.webusername) && Objects.equals(password, this.webpassword)){ ctx.sessionAttribute("username", username); ctx.redirect("/index.html"); } else { - ctx.redirect("/login.html?error=Invalid username or password"); + ctx.status(400); + ctx.redirect("/login.html"); } }); @@ -151,9 +185,12 @@ public class WebServer { if (event!=null) { WebsocketReply reply = event.onWebsocketCommand(command); if (reply!=null) ctx.sendAsClass(reply, WebsocketReply.class); + //if (reply!=null) SendtoAll(reply); } } catch (Exception e){ Logger.error("Failed to parse WebSocketCommand message: {}", e.getMessage()); + ctx.closeSession(); + connectedWebsocketClients.remove(ctx); } }); @@ -196,13 +233,13 @@ public class WebServer { } WsConnectHandler connectws = ws ->{ - Logger.info("WebSocket connected from {}", ws.host()); + Logger.info("WebSocket connected from {}", ws.sessionId()); //ws.headerMap().forEach((key, value) -> Logger.info("HeaderMap {}: {}", key, value)); connectedWebsocketClients.add(ws); }; WsCloseHandler closews = ws ->{ - Logger.info("WebSocket closed from {}, code {}, reason {}", ws.host(), ws.status(), ws.reason()); + Logger.info("WebSocket closed from {}, code {}, reason {}", ws.sessionId(), ws.status(), ws.reason()); connectedWebsocketClients.remove(ws); }; diff --git a/src/main/java/Web/WebsocketEvent.java b/src/main/java/Web/WebsocketEvent.java index fa352eb..dd55039 100644 --- a/src/main/java/Web/WebsocketEvent.java +++ b/src/main/java/Web/WebsocketEvent.java @@ -3,4 +3,5 @@ package Web; public interface WebsocketEvent { String onMessage(String message); WebsocketReply onWebsocketCommand(WebsocketCommand command); + void NewCameraConfiguration(); } diff --git a/src/main/java/id/co/gtc/Main.java b/src/main/java/id/co/gtc/Main.java index 41ccf49..b894008 100644 --- a/src/main/java/id/co/gtc/Main.java +++ b/src/main/java/id/co/gtc/Main.java @@ -2,22 +2,23 @@ package id.co.gtc; import Audio.AudioFileProperties; import Audio.AudioPlayer; +import Audio.Bass; import Audio.PlaybackEvent; import Camera.PanTiltController; import Camera.RtspGrabber; import Camera.VapixProtocol; import Other.SomeCodes; -import Web.WebServer; -import Web.WebsocketCommand; -import Web.WebsocketEvent; -import Web.WebsocketReply; +import SBC.ProcessorStatus; +import SBC.RamInformation; +import SBC.SystemInformation; +import Web.*; +import com.google.gson.JsonObject; import org.bytedeco.opencv.global.opencv_core; import org.tinylog.Logger; import java.io.File; import java.nio.file.Path; -import java.util.Objects; -import java.util.Properties; +import java.util.*; import static Other.SomeCodes.*; @@ -28,6 +29,11 @@ public class Main { private static PanTiltController panTiltController; private static AudioFileProperties audioFileProperties; private static VapixProtocol vapixProtocol; + private static Timer timer; + private static int cpuTemperature; + private static RamInformation ramInformation; + private static ProcessorStatus[] previousCpuInfo; + private static final Map cpuUsage = new HashMap<>(); // Application start from here public static void main(String[] args) { @@ -37,8 +43,11 @@ public class Main { if (rtspGrabber!=null) rtspGrabber.Stop(); if (panTiltController!=null) panTiltController.Close(); if (vapixProtocol!=null) vapixProtocol.Close(); + if (timer!=null) timer.cancel(); })); + init_system_monitoring(); + init_properties(); init_audiofiles(); @@ -49,6 +58,29 @@ public class Main { init_Vapix(); } + private static void init_system_monitoring(){ + TimerTask tt = new TimerTask() { + @Override + public void run() { + cpuTemperature = SystemInformation.getCPUTemperature(); + ramInformation = SystemInformation.getRAMInformation(); + ProcessorStatus[] cpuinfo = SystemInformation.getProcStat(); + if (cpuinfo.length>0){ + if (previousCpuInfo==null || !Objects.equals(previousCpuInfo.length, cpuinfo.length)){ + previousCpuInfo = cpuinfo; + } else { + for(int ii=0;ii0){ + Bass.BASS_DEVICEINFO info = audioPlayer.GetDeviceInfo(devid); + if (audioPlayer.OpenDevice(devid,48000)){ + audioPlayer.setMasterVolume(100); + audioPlayer.setPlaybackvolume(100); + Logger.info("Audio Device ID={} opened : {}", devid, info.name); + } else Logger.error("Failed to open Audio Device ID={}", devid); + } else Logger.error("USB Audio Device not found"); } static PlaybackEvent pe = new PlaybackEvent() { @@ -217,6 +262,7 @@ public class Main { audioFileProperties = afp; audioPlayer.PlayAudioFile(afp, true, pe); 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", "Audio file not found : "+filename); } else return new WebsocketReply("PLAY AUDIO", String.format("AudioFile with ID %02d not found", id)); @@ -252,20 +298,64 @@ public class Main { case "GET BASE64": if (rtspGrabber!=null){ if (Objects.equals(command.data,"HQ")) - return new WebsocketReply("GET BASE64", "data:image/jpeg;base64,"+ rtspGrabber.getLastHQBase64(), String.format("Streaming at %dx%d", rtspGrabber.getHQWidth(), rtspGrabber.getHQHeight())); + return new WebsocketReply("GET BASE64", "data:image/jpeg;base64,"+ rtspGrabber.getLastHQBase64(), rtspGrabber.HQStreamingStatus()); else - return new WebsocketReply("GET BASE64", "data:image/jpeg;base64,"+ rtspGrabber.getLastLQBase64(), String.format("Streaming at %dx%d", rtspGrabber.getLQWidth(), rtspGrabber.getLQHeight())); + return new WebsocketReply("GET BASE64", "data:image/jpeg;base64,"+ rtspGrabber.getLastLQBase64(), rtspGrabber.LQStreamingStatus()); } else return new WebsocketReply("GET BASE64", "RTSP Grabber not initialized"); case "GET RESOLUTION": if (vapixProtocol!=null){ int[] res = vapixProtocol.GetCurrentResolution(1); return new WebsocketReply("GET RESOLUTION", String.format("%dx%d", res[0], res[1])); } else return new WebsocketReply("GET RESOLUTION", "VapixProtocol not initialized"); - + case "GET AUDIOFILES": + AudioFilesInfo afi = new AudioFilesInfo(); + afi.preset1 = GetConfigAudioFile(1); + afi.preset2 = GetConfigAudioFile(2); + afi.preset3 = GetConfigAudioFile(3); + afi.preset4 = GetConfigAudioFile(4); + afi.preset5 = GetConfigAudioFile(5); + return new WebsocketReply("GET AUDIOFILES", gson.toJson(afi)); + case "GET SYSTEM INFO": + JsonObject data = new JsonObject(); + data.addProperty("cpu_temperature", String.valueOf(cpuTemperature)); + if (ramInformation!=null) { + data.addProperty("ram_usage", String.format("%.1f", ramInformation.RamUsagePercentage())); + } + if (!cpuUsage.isEmpty()) { + for(String key : cpuUsage.keySet()){ + data.addProperty(key, String.valueOf(cpuUsage.get(key))); + } + } + return new WebsocketReply("GET SYSTEM INFO", data.toString()); default: return new WebsocketReply("UNKNOWN COMMAND", command.command); } } + + @Override + public void NewCameraConfiguration() { + Logger.info("New Camera Configuration detected"); + Properties prop = SomeCodes.LoadProperties("config.properties"); + String camera_ip = prop.getProperty("Camera_ip"); + String camera_port = prop.getProperty("Camera_port"); + String camera_user = prop.getProperty("Camera_user"); + String camera_password = prop.getProperty("Camera_password"); + String camera_rtsp_path = prop.getProperty("Camera_Rtsp_path"); + Logger.info("Camera Ip: "+camera_ip); + Logger.info("Camera Port: "+camera_port); + Logger.info("Camera User: "+camera_user); + Logger.info("Camera Password: "+camera_password); + Logger.info("Camera Rtsp Path: "+camera_rtsp_path); + + if (rtspGrabber!=null) rtspGrabber.Stop(); + init_rtspgrabber(); + Logger.info("RtspGrabber recreated"); + + if (vapixProtocol!=null) vapixProtocol.Close(); + init_Vapix(); + Logger.info("VapixProtocol recreated"); + + } }; private static void init_webserver() {