commit 16/06/2025

This commit is contained in:
2025-06-17 16:23:50 +07:00
parent f2bb3500c9
commit c617157a0b
33 changed files with 321 additions and 56 deletions

View File

@@ -7,6 +7,7 @@ import mqtt.MqttClient;
import org.tinylog.Logger;
import pa.VX3K;
import pa.VX3KPseudoContactInput;
import web.WebServer;
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
@@ -18,12 +19,14 @@ public class Main {
private static VX3K vx3K;
private static SMTPSender mailsender;
private static ModbusTCPServer modbusServer;
private static WebServer webServer;
// Application entry point
public static void main(String[] args) {
Logger.info("Application started");
// initialize config
config = new config();
config.Load();
// initialize database
db = new Database();
@@ -35,9 +38,14 @@ public class Main {
// initialize Email Sender
mailsender = new SMTPSender(config.getEmail_SMTPServer(), config.getEmail_SMTPPort(), true, config.getEmail_SMTPUsername(), config.getEmail_SMTPPassword());
// initialize Modbus TCP Server
modbusServer = new ModbusTCPServer(config.getModbus_MasterIP(), config.getModbus_Port(), 1000);
modbusServer.Start();
// Initialize the web server
webServer = new WebServer();
webServer.Start(Integer.parseInt(config.getWebListenPort()));
// Initialize the GPIO pins
gpio = new NanopiGpio();
// cek di https://wiki.friendlyelec.com/wiki/index.php/NanoPi_Duo20
@@ -129,6 +137,7 @@ public class Main {
if (gpio!=null && gpio.IsOpened()) gpio.Close();
if (mqttClient!=null && mqttClient.isConnected()) mqttClient.Disconnect();
if (modbusServer!=null && modbusServer.isRunning()) modbusServer.Stop();
if (webServer!=null && webServer.isRunning()) webServer.Stop();
}));
}
}

View File

@@ -6,43 +6,41 @@ import java.nio.file.Files;
import java.nio.file.Path;
public class config {
private @Getter @Setter int Modbus_Port = 502;
private @Getter @Setter String Modbus_MasterIP = "192.168.10.1";
private @Getter @Setter String Modbus_SlaveID = "1";
private @Getter @Setter String VX3KTargetIP;
private @Getter @Setter int VX3KTargetPort;
private @Getter @Setter String Email_SMTPServer;
private @Getter @Setter int Email_SMTPPort;
private @Getter @Setter boolean Email_SMTPSSL;
private @Getter @Setter String Email_SMTPUsername;
private @Getter @Setter String Email_SMTPPassword;
private @Getter @Setter String Email_SMTPFrom ;
private @Getter @Setter String Email_SenderName;
private @Getter @Setter String Email_Subject;
private @Getter @Setter String MQTT_Broker;
private @Getter @Setter int MQTT_Port;
private @Getter @Setter String MQTT_Topic ;
private @Getter @Setter String MQTT_ClientID;
private @Getter @Setter String MQTT_Username;
private @Getter @Setter String MQTT_Password ;
public config(){
Load();
}
@Getter @Setter int Modbus_Port ;
@Getter @Setter String Modbus_MasterIP;
@Getter @Setter String Modbus_SlaveID ;
@Getter @Setter String VX3KTargetIP;
@Getter @Setter int VX3KTargetPort;
@Getter @Setter String Email_SMTPServer;
@Getter @Setter int Email_SMTPPort;
@Getter @Setter boolean Email_SMTPSSL;
@Getter @Setter String Email_SMTPUsername;
@Getter @Setter String Email_SMTPPassword;
@Getter @Setter String Email_SMTPFrom ;
@Getter @Setter String Email_SenderName;
@Getter @Setter String Email_Subject;
@Getter @Setter String MQTT_Broker;
@Getter @Setter int MQTT_Port;
@Getter @Setter String MQTT_Topic ;
@Getter @Setter String MQTT_ClientID;
@Getter @Setter String MQTT_Username;
@Getter @Setter String MQTT_Password ;
@Getter @Setter String WebListenPort;
/**
* Load configuration from file.
* If the file does not exist, create default configuration.
*/
private void Load(){
public void Load(){
Path configPath = Path.of(Somecodes.currentdirectory, "config.json");
if (Files.exists(configPath)){
// Read the configuration from the file
// and if not complete, create defaults
try{
String configContent = Files.readString(configPath);
Logger.info("config content: {}", configContent);
config loadedConfig = Somecodes.gson.fromJson(configContent, config.class);
Logger.info("Loaded config from {}", loadedConfig.toString());
if (loadedConfig != null) {
// Copy values from loadedConfig to this instance
this.Modbus_MasterIP = loadedConfig.Modbus_MasterIP;
@@ -64,6 +62,7 @@ public class config {
this.MQTT_ClientID = loadedConfig.MQTT_ClientID;
this.MQTT_Username = loadedConfig.MQTT_Username;
this.MQTT_Password = loadedConfig.MQTT_Password;
this.WebListenPort = loadedConfig.WebListenPort;
} else {
Logger.error("Loaded config is null, creating Default Config");
@@ -104,7 +103,7 @@ public class config {
MQTT_ClientID = "Pekojan";
MQTT_Username = "gtcdev";
MQTT_Password = "gtcdev2025";
WebListenPort = "80";
Save();
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,57 @@
.bs-icon {
--bs-icon-size: .75rem;
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
font-size: var(--bs-icon-size);
width: calc(var(--bs-icon-size) * 2);
height: calc(var(--bs-icon-size) * 2);
color: var(--bs-primary);
}
.bs-icon-xs {
--bs-icon-size: 1rem;
width: calc(var(--bs-icon-size) * 1.5);
height: calc(var(--bs-icon-size) * 1.5);
}
.bs-icon-sm {
--bs-icon-size: 1rem;
}
.bs-icon-md {
--bs-icon-size: 1.5rem;
}
.bs-icon-lg {
--bs-icon-size: 2rem;
}
.bs-icon-xl {
--bs-icon-size: 2.5rem;
}
.bs-icon.bs-icon-primary {
color: var(--bs-white);
background: var(--bs-primary);
}
.bs-icon.bs-icon-primary-light {
color: var(--bs-primary);
background: rgba(var(--bs-primary-rgb), .2);
}
.bs-icon.bs-icon-semi-white {
color: var(--bs-primary);
background: rgba(255, 255, 255, .5);
}
.bs-icon.bs-icon-rounded {
border-radius: .5rem;
}
.bs-icon.bs-icon-circle {
border-radius: 50%;
}

View File

@@ -0,0 +1,22 @@
document.addEventListener("DOMContentLoaded", function () {
const socket = new WebSocket("ws://" + location.host + "/ws");
socket.onopen = () => {
console.log("WebSocket connected");
const message = { type: "hello", value: 123 };
socket.send(JSON.stringify(message));
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Received from server:", data);
};
socket.onerror = (err) => {
console.error("WebSocket error:", err);
};
socket.onclose = () => {
console.log("WebSocket closed");
};
});

32
src/html/index.html Normal file
View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html data-bs-theme="light" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>FireAlarmGateway</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/Navbar-Centered-Brand-icons.css">
</head>
<body>
<nav class="navbar navbar-expand-md bg-body py-3">
<div class="container"><a class="navbar-brand d-flex align-items-center" href="#"><span class="bs-icon-sm bs-icon-rounded bs-icon-primary d-flex justify-content-center align-items-center me-2 bs-icon"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-bezier">
<path fill-rule="evenodd" d="M0 10.5A1.5 1.5 0 0 1 1.5 9h1A1.5 1.5 0 0 1 4 10.5v1A1.5 1.5 0 0 1 2.5 13h-1A1.5 1.5 0 0 1 0 11.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm10.5.5A1.5 1.5 0 0 1 13.5 9h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zM6 4.5A1.5 1.5 0 0 1 7.5 3h1A1.5 1.5 0 0 1 10 4.5v1A1.5 1.5 0 0 1 8.5 7h-1A1.5 1.5 0 0 1 6 5.5zM7.5 4a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5z"></path>
<path d="M6 4.5H1.866a1 1 0 1 0 0 1h2.668A6.517 6.517 0 0 0 1.814 9H2.5c.123 0 .244.015.358.043a5.517 5.517 0 0 1 3.185-3.185A1.503 1.503 0 0 1 6 5.5zm3.957 1.358A1.5 1.5 0 0 0 10 5.5v-1h4.134a1 1 0 1 1 0 1h-2.668a6.517 6.517 0 0 1 2.72 3.5H13.5c-.123 0-.243.015-.358.043a5.517 5.517 0 0 0-3.185-3.185z"></path>
</svg></span><span>Contact Input Status</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-4"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse flex-grow-0 order-md-first" id="navcol-4">
<ul class="navbar-nav me-auto">
<li class="nav-item"><a class="nav-link active" href="#">Contact Input</a></li>
<li class="nav-item"><a class="nav-link" href="setting.html">Setting</a></li>
</ul>
<div class="d-md-none my-2"><button class="btn btn-light me-2" type="button">Button</button><button class="btn btn-primary" type="button">Button</button></div>
</div>
<div class="d-none d-md-block"><a class="btn btn-primary" role="button" href="#">Login</a></div>
</div>
</nav>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/setting.js"></script>
</body>
</html>

58
src/html/setting.html Normal file
View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html data-bs-theme="light" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>FireAlarmGateway</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/Navbar-Centered-Brand-icons.css">
</head>
<body>
<nav class="navbar navbar-expand-md bg-body py-3">
<div class="container"><a class="navbar-brand d-flex align-items-center" href="#"><span class="bs-icon-sm bs-icon-rounded bs-icon-primary d-flex justify-content-center align-items-center me-2 bs-icon"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-bezier">
<path fill-rule="evenodd" d="M0 10.5A1.5 1.5 0 0 1 1.5 9h1A1.5 1.5 0 0 1 4 10.5v1A1.5 1.5 0 0 1 2.5 13h-1A1.5 1.5 0 0 1 0 11.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm10.5.5A1.5 1.5 0 0 1 13.5 9h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zM6 4.5A1.5 1.5 0 0 1 7.5 3h1A1.5 1.5 0 0 1 10 4.5v1A1.5 1.5 0 0 1 8.5 7h-1A1.5 1.5 0 0 1 6 5.5zM7.5 4a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5z"></path>
<path d="M6 4.5H1.866a1 1 0 1 0 0 1h2.668A6.517 6.517 0 0 0 1.814 9H2.5c.123 0 .244.015.358.043a5.517 5.517 0 0 1 3.185-3.185A1.503 1.503 0 0 1 6 5.5zm3.957 1.358A1.5 1.5 0 0 0 10 5.5v-1h4.134a1 1 0 1 1 0 1h-2.668a6.517 6.517 0 0 1 2.72 3.5H13.5c-.123 0-.243.015-.358.043a5.517 5.517 0 0 0-3.185-3.185z"></path>
</svg></span><span>Settings</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-4"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse flex-grow-0 order-md-first" id="navcol-4">
<ul class="navbar-nav me-auto">
<li class="nav-item"><a class="nav-link active" href="index.html">Contact Input</a></li>
<li class="nav-item"><a class="nav-link" href="#">Setting</a></li>
</ul>
<div class="d-md-none my-2"><button class="btn btn-light me-2" type="button">Button</button><button class="btn btn-primary" type="button">Button</button></div>
</div>
<div class="d-none d-md-block"><a class="btn btn-primary" role="button" href="#">Login</a></div>
</div>
</nav>
<div class="vstack">
<div class="container">
<div class="row">
<div class="col-md-6"><label class="col-form-label w-100 h-100">SMTP Server</label></div>
<div class="col-md-6"><input class="w-100 h-100" type="text"></div>
</div>
<div class="row">
<div class="col-md-6"><label class="col-form-label w-100 h-100">SMTP Port</label></div>
<div class="col-md-6"><input class="w-100 h-100" type="text"></div>
</div>
<div class="row">
<div class="col-md-6"><label class="col-form-label w-100 h-100">SMTP Username</label></div>
<div class="col-md-6"><input class="w-100 h-100" type="text"></div>
</div>
<div class="row">
<div class="col-md-6"><label class="col-form-label w-100 h-100">SMTP Password</label></div>
<div class="col-md-6"><input class="w-100 h-100" type="text"></div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-md-6"><button class="btn btn-primary w-100 h-100" type="button">Reset Default</button></div>
<div class="col-md-6"><button class="btn btn-primary w-100 h-100" type="button">Apply</button></div>
</div>
</div>
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/setting.js"></script>
</body>
</html>

74
src/web/WebServer.java Normal file
View File

@@ -0,0 +1,74 @@
package web;
import io.javalin.Javalin;
import lombok.Getter;
import org.tinylog.Logger;
public class WebServer {
private Javalin app;
private @Getter boolean isRunning = false;
/**
* Start the web server on the specified port.
* @param listenport the port to listen on
*/
public void Start(int listenport){
isRunning = false;
app = null;
try{
var xx = Javalin.create(config -> {
config.useVirtualThreads = true;
config.staticFiles.add("/html");
}).start(listenport);
isRunning = true;
app = xx;
Logger.info("Web server started on port {}", listenport);
AssignRoutes();
} catch (Exception e){
Logger.error("Failed to start web server: {}", e.getMessage());
}
}
private void AssignRoutes(){
if (app == null) {
Logger.error("Web server is not running, cannot assign routes.");
return;
}
app.ws("/ws", ws->{
ws.onConnect(ctx -> {
Logger.info("WebSocket connected: {}", ctx.session.getRemoteAddress());
});
ws.onMessage(ctx -> {
Logger.info("WebSocket message received: {}", ctx.message());
// Handle incoming messages here
});
ws.onClose(ctx -> {
Logger.info("WebSocket closed: {} ", ctx.session.getRemoteAddress());
});
ws.onError(ctx -> {
Logger.error("WebSocket error: {}", ctx.error().getMessage());
});
});
}
/**
* Stop the web server if it is running.
*/
public void Stop(){
if (app!=null){
try{
app.stop();
} catch (Exception e){
Logger.error("Failed to stop web server: {}", e.getMessage());
}
app = null;
}
isRunning = false;
}
}