first commit

This commit is contained in:
2025-06-14 14:25:30 +07:00
parent d9b005f784
commit 241fa0edd9
6 changed files with 208 additions and 0 deletions

View File

@@ -1,3 +1,4 @@
import gpio.NanopiGpio;
import mqtt.MqttClient;
import org.tinylog.Logger;
@@ -6,9 +7,39 @@ import org.tinylog.Logger;
public class Main {
private static config config;
private static MqttClient mqttClient;
private static NanopiGpio gpio;
// Application entry point
public static void main(String[] args) {
Logger.info("Application started");
config = new config();
// Initialize the GPIO pins
gpio = new NanopiGpio();
// cek di https://wiki.friendlyelec.com/wiki/index.php/NanoPi_Duo20
gpio.Open(opened -> {
if (opened) {
Logger.info("GPIO pins opened successfully.");
} else {
Logger.error("GPIO pins closed or failed to open.");
}
}, pinStatus -> {
// TODO Handle pin status updates here
Logger.info("Gpio {}, Description {}, status updated to {}", pinStatus.getGpioNumber(), pinStatus.getDescription(), pinStatus.getStatus());
},
new gpio.PinInfo(11, "Relay 1"),
new gpio.PinInfo(12, "Relay 2"),
new gpio.PinInfo(13, "Relay 3"),
new gpio.PinInfo(14, "Relay 4"),
new gpio.PinInfo(16, "Relay 5"),
new gpio.PinInfo(15, "Relay 6"),
new gpio.PinInfo(199, "Relay 7"),
new gpio.PinInfo(198, "Relay 8")
);
// initialize the MQTT client
mqttClient = new MqttClient(config.getMQTT_Broker(), config.getMQTT_Port(), config.getMQTT_Username(), config.getMQTT_Password(), connected -> {
if (connected) {
Logger.info("MQTT client connected successfully.");
@@ -23,5 +54,10 @@ public class Main {
Logger.error("Failed to connect MQTT client.");
}
});
// Add a shutdown hook to disconnect all services
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (mqttClient!=null && mqttClient.isConnected()) mqttClient.Disconnect();
}));
}
}

9
src/gpio/Gpio.java Normal file
View File

@@ -0,0 +1,9 @@
package gpio;
import java.util.function.Consumer;
public interface Gpio {
void Open(Consumer<Boolean> callback, Consumer<PinStatus> pinStatus, PinInfo... pinInfos);
void Close();
boolean IsOpened();
}

123
src/gpio/NanopiGpio.java Normal file
View File

@@ -0,0 +1,123 @@
package gpio;
import org.tinylog.Logger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class NanopiGpio implements Gpio {
private boolean isopened = false;
private final Path gpioPath = Path.of("/sys/class/gpio") ;
private final Path gpioExportPath = gpioPath.resolve( "export" ) ;
private final Path gpioUnexportPath = gpioPath.resolve( "unexport" ) ;
private final boolean IsLinux = System.getProperty("os.name").toLowerCase().contains("linux");
private final List<PinInfo> openedPins;
public NanopiGpio(){
openedPins = new ArrayList<>();
}
@Override
public boolean IsOpened() {
return isopened;
}
@SuppressWarnings({"BusyWait"})
@Override
public void Open(Consumer<Boolean> OpenedCallback, Consumer<PinStatus> pinStatus, PinInfo... pinInfos) {
if (IsLinux){
if (Files.exists(gpioExportPath) && Files.exists(gpioUnexportPath)){
if (pinInfos!=null && pinInfos.length>0) {
openedPins.clear();
for(PinInfo pinInfo : pinInfos){
try{
Files.writeString(gpioExportPath, String.valueOf(pinInfo.GpioNumber()));
Logger.info("Exported GPIO pin: {}", pinInfo.GpioNumber());
openedPins.add(pinInfo);
} catch (Exception ex){
Logger.error("Error while processing GPIO pin {}: {}", pinInfo.GpioNumber(), ex.getMessage());
}
}
if (openedPins.size()==pinInfos.length){
isopened = true;
Thread tx = new Thread(() -> {
List<PinStatus> pinStatusList = new ArrayList<>();
for (PinInfo pinInfo : openedPins) {
pinStatusList.add(new PinStatus(pinInfo.GpioNumber(), pinInfo.Description(), -1));
}
if (OpenedCallback != null) OpenedCallback.accept(true);
Logger.info("Gpio Scanner started");
while (isopened){
try{
Thread.sleep(20);
for(PinStatus status : pinStatusList){
try {
String value = Files.readString(gpioPath.resolve("gpio" + status.getGpioNumber() + "/value")).trim();
if (value.equals("1")) {
if (status.getStatus()!=1){
status.setStatus(1);
if (pinStatus != null) {
pinStatus.accept(status); // Notify the callback with the updated status
}
}
} else if (value.equals("0")) {
if (status.getStatus()!=0){
status.setStatus(0);
if (pinStatus != null) {
pinStatus.accept(status); // Notify the callback with the updated status
}
}
} else {
status.setStatus(-1); // Set status to -1 if value is not 0 or 1
}
} catch (Exception e) {
Logger.error("Error reading GPIO pin {}: {}", status.getGpioNumber(), e.getMessage());
status.setStatus(-1); // Set status to -1 on error
}
}
} catch (InterruptedException ignored){}
}
Logger.info("Gpio Scanner stopped");
if (OpenedCallback!=null) OpenedCallback.accept(false);
});
tx.setName("GpioScanner");
tx.setDaemon(true);
tx.start();
return;
}
} else Logger.error("Nothing to open, no pinInfos provided.");
} else Logger.error("GPIO paths do not exist: {} or {}", gpioExportPath, gpioUnexportPath);
} else Logger.error("This GPIO implementation is only supported on Linux systems.");
if (OpenedCallback!=null) OpenedCallback.accept(false);
}
@Override
public void Close() {
isopened = false;
if (!openedPins.isEmpty()){
openedPins.forEach(p -> {
try {
Files.writeString(gpioUnexportPath, String.valueOf(p.GpioNumber()));
Logger.info("Unexported GPIO pin: {}", p.GpioNumber());
} catch (Exception e) {
Logger.error("Error while unexporting GPIO pin {}: {}", p.GpioNumber(), e.getMessage());
}
});
openedPins.clear();
}
}
}

4
src/gpio/PinInfo.java Normal file
View File

@@ -0,0 +1,4 @@
package gpio;
public record PinInfo(int GpioNumber, String Description) {
}

17
src/gpio/PinStatus.java Normal file
View File

@@ -0,0 +1,17 @@
package gpio;
import lombok.Getter;
import lombok.Setter;
@Getter
public class PinStatus {
private final int GpioNumber;
private final String Description;
private @Setter int Status;
public PinStatus(int GpioNumber, String Description, int status) {
this.Description = Description;
this.GpioNumber = GpioNumber;
this.Status = status;
}
}

View File

@@ -3,6 +3,7 @@ package mqtt;
import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient;
import com.hivemq.client.mqtt.mqtt3.Mqtt3Client;
import com.hivemq.client.mqtt.mqtt3.message.auth.Mqtt3SimpleAuth;
import lombok.Getter;
import org.tinylog.Logger;
import java.nio.charset.StandardCharsets;
@@ -11,6 +12,7 @@ import java.util.function.Consumer;
public class MqttClient {
Mqtt3AsyncClient client;
private @Getter boolean connected = false;
/**
* Constructor to create an MQTT client and connect to the broker.
@@ -22,6 +24,7 @@ public class MqttClient {
* @param connectedCallback A callback that is called when the connection is established or fails.
*/
public MqttClient(String host, int port, String username, String password, Consumer<Boolean> connectedCallback) {
Mqtt3AsyncClient clientx = Mqtt3Client.builder()
.simpleAuth(Mqtt3SimpleAuth.builder().username(username).password(password.getBytes(StandardCharsets.UTF_8)).build())
.serverHost(host)
@@ -42,10 +45,26 @@ public class MqttClient {
if (connectedCallback != null) {
connectedCallback.accept(true);
}
connected = true;
}
});
}
public void Disconnect(){
if (client!=null){
client.disconnect()
.whenComplete((disconn, throwable) -> {
if (throwable != null) {
Logger.error("Failed to disconnect from MQTT broker, Message : {}", throwable.getMessage());
} else {
Logger.info("Disconnected from MQTT broker.");
}
connected = false;
client = null;
});
}
}
public void Publish(String topic, String clientID, String message, Consumer<Boolean> publishCallback) {
if (client == null) {
Logger.error("Cannot publish message, MQTT client is not connected.");