hardware n monitoring
This commit is contained in:
parent
1cf7a02ba7
commit
5e38ec30bc
@ -74,7 +74,7 @@ func main() {
|
|||||||
|
|
||||||
r.GET("/api/hardware/power-status", controllers.GetPowerStatus)
|
r.GET("/api/hardware/power-status", controllers.GetPowerStatus)
|
||||||
|
|
||||||
r.POST("/api/cutoff", controllers.CutOffAllPower)
|
r.POST("/api/power/global", controllers.GlobalPowerControl)
|
||||||
|
|
||||||
r.Run(":8080")
|
r.Run(":8080")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,11 @@ type VerifyRequest struct {
|
|||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tambahan untuk kontrol daya global (ON/OFF)
|
||||||
|
type GlobalPowerRequest struct {
|
||||||
|
Action string `json:"action"` // "on" atau "off"
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// CACHE STATUS HARDWARE (Mengingat status terakhir perangkat untuk Web Admin)
|
// CACHE STATUS HARDWARE (Mengingat status terakhir perangkat untuk Web Admin)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@ -313,42 +318,69 @@ func GetPowerStatus(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// FUNGSI 4: CUT OFF SEMUA POWER (MATIKAN 3 MCB)
|
// FUNGSI 4: GLOBAL POWER CONTROL (Sequential ON/OFF dengan Jeda 1 Detik)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
func CutOffAllPower(c *gin.Context) {
|
func GlobalPowerControl(c *gin.Context) {
|
||||||
|
var req GlobalPowerRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Format data tidak valid"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
haURL := os.Getenv("HA_URL")
|
haURL := os.Getenv("HA_URL")
|
||||||
haToken := os.Getenv("HA_TOKEN")
|
haToken := os.Getenv("HA_TOKEN")
|
||||||
|
|
||||||
apiURL := fmt.Sprintf("%s/api/services/switch/turn_off", haURL)
|
// Tentukan service HA berdasarkan request
|
||||||
|
service := "turn_off"
|
||||||
|
if req.Action == "on" {
|
||||||
|
service = "turn_on"
|
||||||
|
}
|
||||||
|
apiURL := fmt.Sprintf("%s/api/services/switch/%s", haURL, service)
|
||||||
|
|
||||||
payload := map[string]interface{}{
|
// Daftar MCB yang akan dieksekusi secara berurutan
|
||||||
"entity_id": []string{
|
mcbs := []string{
|
||||||
"switch.wifi_smart_meter_pro_switch",
|
"switch.wifi_smart_meter_pro_switch", // Umum
|
||||||
"switch.wifi_smart_meter_pro_2_switch",
|
"switch.wifi_smart_meter_pro_2_switch", // AC1
|
||||||
"switch.wifi_smart_meter_pro_3_switch",
|
"switch.wifi_smart_meter_pro_3_switch", // AC2
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonPayload, _ := json.Marshal(payload)
|
// Looping untuk eksekusi satu per satu
|
||||||
req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonPayload))
|
for i, mcb := range mcbs {
|
||||||
req.Header.Set("Authorization", "Bearer "+haToken)
|
payload := map[string]string{"entity_id": mcb}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
jsonPayload, _ := json.Marshal(payload)
|
||||||
|
|
||||||
client := &http.Client{Timeout: 5 * time.Second}
|
reqHA, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonPayload))
|
||||||
resp, err := client.Do(req)
|
if err == nil {
|
||||||
if err != nil {
|
reqHA.Header.Set("Authorization", "Bearer "+haToken)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Gagal menghubungi Home Assistant"})
|
reqHA.Header.Set("Content-Type", "application/json")
|
||||||
return
|
|
||||||
|
client := &http.Client{Timeout: 5 * time.Second}
|
||||||
|
resp, err := client.Do(reqHA)
|
||||||
|
if err == nil && resp != nil {
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[MCB] Sinyal %s dikirim ke %s\n", req.Action, mcb)
|
||||||
|
|
||||||
|
// Berikan jeda 1 detik, KECUALI untuk iterasi terakhir
|
||||||
|
if i < len(mcbs)-1 {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// Update cache
|
// Jika dimatikan paksa, update cache perangkat IoT menjadi mati semua
|
||||||
DeviceStatusCache["lampu1"] = "off"
|
if req.Action == "off" {
|
||||||
DeviceStatusCache["lampu2"] = "off"
|
DeviceStatusCache["lampu1"] = "off"
|
||||||
DeviceStatusCache["ac"] = "off"
|
DeviceStatusCache["lampu2"] = "off"
|
||||||
DeviceStatusCache["projector"] = "off"
|
DeviceStatusCache["ac"] = "off"
|
||||||
|
DeviceStatusCache["projector"] = "off"
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"status": "success", "message": "Semua daya ruangan (MCB) berhasil diputus"})
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": "success",
|
||||||
|
"message": fmt.Sprintf("Seluruh daya ruangan berhasil di-%s secara berurutan", req.Action),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Activity, Power, ZapOff, AlertTriangle, Lightbulb, Wind, Projector } from "lucide-react";
|
import { Activity, Power, ZapOff, Zap, AlertTriangle, Lightbulb, Wind, Projector } from "lucide-react";
|
||||||
|
|
||||||
export default function PowerMonitoringPage() {
|
export default function PowerMonitoringPage() {
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@ -147,27 +147,44 @@ export default function PowerMonitoringPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// FUNGSI CUT OFF DAYA KHUSUS D101
|
// FUNGSI GLOBAL POWER (ON/OFF KHUSUS D101)
|
||||||
const handleCutOff = async (roomName: string, roomId: number) => {
|
const handleGlobalPower = async (roomName: string, roomId: number, currentRelayStatus: boolean) => {
|
||||||
if (roomName === "Kelas D101") {
|
if (roomName === "Kelas D101") {
|
||||||
if (!window.confirm(`PERINGATAN FATAL: Anda yakin ingin memutus 3 MCB Daya di ${roomName} secara paksa?`)) return;
|
const actionType = currentRelayStatus ? "off" : "on";
|
||||||
|
const confirmMessage = currentRelayStatus
|
||||||
|
? `PERINGATAN FATAL: Anda yakin ingin MEMUTUS 3 MCB Daya di ${roomName}? (Proses berurutan ~3 detik)`
|
||||||
|
: `RESTORE POWER: Anda yakin ingin MENYALAKAN KEMBALI 3 MCB Daya di ${roomName}? (Proses berurutan ~3 detik)`;
|
||||||
|
|
||||||
|
if (!window.confirm(confirmMessage)) return;
|
||||||
|
|
||||||
|
// Ubah UI seketika
|
||||||
|
setRooms(prev => prev.map(r => r.name === roomName ? { ...r, isRelayOn: !currentRelayStatus } : r));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("http://172.17.172.17:8080/api/cutoff", { method: "POST" });
|
const response = await fetch("http://172.17.172.17:8080/api/power/global", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ action: actionType }),
|
||||||
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
alert(`Daya di ${roomName} berhasil diputus!`);
|
alert(`Daya di ${roomName} berhasil di-${actionType.toUpperCase()}!`);
|
||||||
setRooms(prev => prev.map(r => r.name === roomName ? { ...r, isRelayOn: false, power: 0 } : r));
|
|
||||||
fetchPowerStatus();
|
fetchPowerStatus();
|
||||||
|
fetchDeviceStatus();
|
||||||
} else {
|
} else {
|
||||||
alert("Gagal memutus daya. Cek koneksi server.");
|
// Rollback UI jika gagal
|
||||||
|
setRooms(prev => prev.map(r => r.name === roomName ? { ...r, isRelayOn: currentRelayStatus } : r));
|
||||||
|
alert("Gagal mengontrol MCB utama. Cek koneksi server.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert("Terjadi kesalahan jaringan saat memutus daya.");
|
setRooms(prev => prev.map(r => r.name === roomName ? { ...r, isRelayOn: currentRelayStatus } : r));
|
||||||
|
alert("Terjadi kesalahan jaringan saat menghubungi server.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Dummy untuk kelas lain
|
// Dummy untuk kelas lain
|
||||||
if (window.confirm(`Simulasi mematikan daya di ${roomName}?`)) {
|
const actionType = currentRelayStatus ? "Mematikan" : "Menyalakan";
|
||||||
setRooms(prev => prev.map(r => r.name === roomName ? { ...r, isRelayOn: false, power: 0 } : r));
|
if (window.confirm(`Simulasi ${actionType} daya di ${roomName}?`)) {
|
||||||
|
setRooms(prev => prev.map(r => r.name === roomName ? { ...r, isRelayOn: !currentRelayStatus, power: 0 } : r));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -275,14 +292,13 @@ export default function PowerMonitoringPage() {
|
|||||||
<div className="flex items-center justify-between mt-6 pt-4 border-t border-gray-100">
|
<div className="flex items-center justify-between mt-6 pt-4 border-t border-gray-100">
|
||||||
<span className="text-xs text-gray-400 font-medium">Update: {room.lastUpdate}</span>
|
<span className="text-xs text-gray-400 font-medium">Update: {room.lastUpdate}</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleCutOff(room.name, room.id)}
|
onClick={() => handleGlobalPower(room.name, room.id, room.isRelayOn)}
|
||||||
disabled={!room.isRelayOn}
|
|
||||||
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-xs font-bold transition-all
|
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-xs font-bold transition-all
|
||||||
${room.isRelayOn
|
${room.isRelayOn
|
||||||
? 'bg-red-50 text-red-600 hover:bg-red-600 hover:text-white border border-red-200 hover:border-red-600'
|
? 'bg-red-50 text-red-600 hover:bg-red-600 hover:text-white border border-red-200 hover:border-red-600'
|
||||||
: 'bg-gray-100 text-gray-400 cursor-not-allowed'}`}
|
: 'bg-green-50 text-green-600 hover:bg-green-600 hover:text-white border border-green-200 hover:border-green-600'}`}
|
||||||
>
|
>
|
||||||
{room.isRelayOn ? <><Power size={14} /> Cut Off Power</> : <><ZapOff size={14} /> Offline</>}
|
{room.isRelayOn ? <><Power size={14} /> Cut Off Power</> : <><Zap size={14} /> Restore Power</>}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user