fix all pengumuman
This commit is contained in:
parent
b43c74e5ad
commit
4448c83266
|
@ -1,20 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace App\Events;
|
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
|
||||||
|
|
||||||
class BellRingEvent implements ShouldBroadcast {
|
|
||||||
use Dispatchable, InteractsWithSockets;
|
|
||||||
|
|
||||||
public $data;
|
|
||||||
|
|
||||||
public function __construct(array $data) {
|
|
||||||
$this->data = $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function broadcastOn() {
|
|
||||||
return new Channel('bell-channel');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,45 +5,99 @@
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\BellHistory;
|
use App\Models\BellHistory;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
class BellController extends Controller
|
class BellController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Menyimpan data event bel dari ESP32
|
* Menyimpan data event bel manual dari ESP32
|
||||||
|
*/
|
||||||
|
public function storeManualEvent(Request $request)
|
||||||
|
{
|
||||||
|
return $this->storeEvent($request, 'manual');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menyimpan data event bel schedule dari ESP32
|
||||||
*/
|
*/
|
||||||
public function storeScheduleEvent(Request $request)
|
public function storeScheduleEvent(Request $request)
|
||||||
|
{
|
||||||
|
return $this->storeEvent($request, 'schedule');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fungsi privat untuk menyimpan event (digunakan oleh manual dan schedule)
|
||||||
|
*/
|
||||||
|
private function storeEvent(Request $request, string $triggerType)
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'hari' => 'required|in:Senin,Selasa,Rabu,Kamis,Jumat,Sabtu,Minggu',
|
'hari' => 'required|in:Senin,Selasa,Rabu,Kamis,Jumat,Sabtu,Minggu',
|
||||||
'waktu' => 'required|date_format:H:i:s',
|
'waktu' => 'required|date_format:H:i:s',
|
||||||
'file_number' => 'required|string|size:4',
|
'file_number' => 'required|string|size:4',
|
||||||
'volume' => 'sometimes|integer|min:0|max:30',
|
'volume' => 'sometimes|integer|min:0|max:30',
|
||||||
'repeat' => 'sometimes|integer|min:1|max:5'
|
'repeat' => 'sometimes|integer|min:1|max:5',
|
||||||
|
'trigger_type' => 'sometimes|in:manual,schedule' // jika dikirim dari client
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Tambahkan trigger_type secara otomatis
|
// Set default values jika tidak ada
|
||||||
$validated['trigger_type'] = 'schedule';
|
$validated['volume'] = $validated['volume'] ?? 15;
|
||||||
$validated['ring_time'] = now();
|
$validated['repeat'] = $validated['repeat'] ?? 1;
|
||||||
|
|
||||||
|
// Override trigger_type dengan nilai dari parameter
|
||||||
|
$validated['trigger_type'] = $triggerType;
|
||||||
|
$validated['ring_time'] = Carbon::now();
|
||||||
|
|
||||||
|
$exists = BellHistory::where('hari', $validated['hari'])
|
||||||
|
->where('waktu', $validated['waktu'])
|
||||||
|
->where('file_number', $validated['file_number'])
|
||||||
|
->where('trigger_type', $triggerType)
|
||||||
|
->where('ring_time', '>=', Carbon::now()->subSeconds(60))
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($exists) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Event already recorded recently, skipping duplicate.'
|
||||||
|
], 409);
|
||||||
|
}
|
||||||
|
|
||||||
$history = BellHistory::create($validated);
|
$history = BellHistory::create($validated);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
|
'message' => 'Bell event recorded successfully',
|
||||||
'data' => $history
|
'data' => $history
|
||||||
], 201);
|
], 201);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mengambil data history untuk ESP32 (jika diperlukan)
|
* Mengambil data history untuk ESP32
|
||||||
*/
|
*/
|
||||||
public function getHistory(Request $request)
|
public function getHistory(Request $request)
|
||||||
{
|
{
|
||||||
$histories = BellHistory::orderBy('ring_time', 'desc')
|
$request->validate([
|
||||||
->limit(50)
|
'limit' => 'sometimes|integer|min:1|max:100',
|
||||||
->get();
|
'days' => 'sometimes|integer|min:1|max:30',
|
||||||
|
'trigger_type' => 'sometimes|in:manual,schedule'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$query = BellHistory::query()
|
||||||
|
->orderBy('ring_time', 'desc');
|
||||||
|
|
||||||
|
if ($request->has('trigger_type')) {
|
||||||
|
$query->where('trigger_type', $request->trigger_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('days')) {
|
||||||
|
$query->where('ring_time', '>=', Carbon::now()->subDays($request->days));
|
||||||
|
}
|
||||||
|
|
||||||
|
$limit = $request->input('limit', 50);
|
||||||
|
$histories = $query->limit($limit)->get();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
|
'count' => $histories->count(),
|
||||||
'data' => $histories
|
'data' => $histories
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,19 +6,39 @@
|
||||||
use App\Models\Ruangan;
|
use App\Models\Ruangan;
|
||||||
use App\Http\Requests\StoreAnnouncementRequest;
|
use App\Http\Requests\StoreAnnouncementRequest;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use PhpMqtt\Client\Facades\MQTT;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Services\MqttService;
|
||||||
|
|
||||||
class AnnouncementController extends Controller
|
class AnnouncementController extends Controller
|
||||||
{
|
{
|
||||||
|
// Constants for modes and actions
|
||||||
|
const MODE_TTS = 'tts';
|
||||||
|
const MODE_MANUAL = 'manual';
|
||||||
|
const ACTION_ACTIVATE = 'activate';
|
||||||
|
const ACTION_DEACTIVATE = 'deactivate';
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$ruangans = Ruangan::orderBy('nama_ruangan')->get();
|
$ruangans = Ruangan::orderBy('nama_ruangan')->get();
|
||||||
$mqttStatus = $this->checkMqttConnection();
|
$mqttService = app(MqttService::class);
|
||||||
return view('admin.announcement.index', compact('ruangans', 'mqttStatus'));
|
|
||||||
|
return view('admin.announcement.index', [
|
||||||
|
'ruangans' => $ruangans,
|
||||||
|
'mqttStatus' => $mqttService->isConnected(),
|
||||||
|
'modes' => [self::MODE_TTS, self::MODE_MANUAL]
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Failed to load announcement index: ' . $e->getMessage());
|
||||||
|
return back()->with('error', 'Gagal memuat halaman pengumuman');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function history()
|
public function history()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$announcements = Announcement::with('ruangans')
|
$announcements = Announcement::with('ruangans')
|
||||||
->when(request('mode'), function($query, $mode) {
|
->when(request('mode'), function($query, $mode) {
|
||||||
return $query->where('mode', $mode);
|
return $query->where('mode', $mode);
|
||||||
|
@ -26,128 +46,194 @@ public function history()
|
||||||
->when(request('date'), function($query, $date) {
|
->when(request('date'), function($query, $date) {
|
||||||
return $query->whereDate('sent_at', $date);
|
return $query->whereDate('sent_at', $date);
|
||||||
})
|
})
|
||||||
|
->when(request('ruangan'), function($query, $ruanganId) {
|
||||||
|
return $query->whereHas('ruangans', function($q) use ($ruanganId) {
|
||||||
|
$q->where('ruangan.id', $ruanganId);
|
||||||
|
});
|
||||||
|
})
|
||||||
->orderBy('sent_at', 'desc')
|
->orderBy('sent_at', 'desc')
|
||||||
->paginate(10);
|
->paginate(10)
|
||||||
|
->withQueryString();
|
||||||
|
|
||||||
return view('admin.announcement.history', compact('announcements'));
|
$ruangans = Ruangan::orderBy('nama_ruangan')->get();
|
||||||
}
|
|
||||||
|
return view('admin.announcement.history', [
|
||||||
|
'announcements' => $announcements,
|
||||||
|
'ruangans' => $ruangans,
|
||||||
|
'modes' => [self::MODE_TTS, self::MODE_MANUAL]
|
||||||
|
]);
|
||||||
|
|
||||||
private function checkMqttConnection()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$mqtt = MQTT::connection();
|
|
||||||
return $mqtt->isConnected();
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return false;
|
Log::error('Failed to load announcement history: ' . $e->getMessage());
|
||||||
|
return back()->with('error', 'Gagal memuat riwayat pengumuman');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(StoreAnnouncementRequest $request)
|
public function store(StoreAnnouncementRequest $request)
|
||||||
{
|
{
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
$announcementData = [
|
$announcementData = [
|
||||||
'mode' => $request->mode,
|
'mode' => $request->mode,
|
||||||
'sent_at' => now(),
|
'sent_at' => now(),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Hanya tambahkan message jika mode TTS
|
if ($request->mode === self::MODE_TTS) {
|
||||||
if ($request->mode === 'tts') {
|
|
||||||
$announcementData['message'] = $request->message;
|
$announcementData['message'] = $request->message;
|
||||||
}
|
}
|
||||||
|
|
||||||
$announcement = Announcement::create($announcementData);
|
$announcement = Announcement::create($announcementData);
|
||||||
$announcement->ruangans()->sync($request->ruangans);
|
$announcement->ruangans()->sync($request->ruangans);
|
||||||
|
|
||||||
try {
|
$mqttService = app(MqttService::class);
|
||||||
if ($request->mode === 'tts') {
|
|
||||||
MQTT::publish('control/relay', json_encode([
|
|
||||||
'mode' => 'tts',
|
|
||||||
'ruang' => $request->ruangans
|
|
||||||
]));
|
|
||||||
|
|
||||||
MQTT::publish('tts/play', json_encode([
|
if ($request->mode === self::MODE_TTS) {
|
||||||
'ruang' => $request->ruangans,
|
$success = $mqttService->sendTTSAnnouncement(
|
||||||
'teks' => $request->message
|
$request->ruangans,
|
||||||
]));
|
$request->message
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
MQTT::publish('control/relay', json_encode([
|
$success = $mqttService->sendRelayControl(
|
||||||
'mode' => 'reguler',
|
'activate', // Default action for announcements
|
||||||
'ruang' => $request->ruangans
|
$request->ruangans,
|
||||||
]));
|
$request->mode
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
|
||||||
|
if (!$success) {
|
||||||
|
throw new \Exception('Gagal mengirim perintah ke perangkat');
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Gagal mengirim ke perangkat: ' . $e->getMessage()
|
'success' => true,
|
||||||
|
'message' => 'Pengumuman berhasil dikirim'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
Log::error('Failed to store announcement: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Gagal mengirim pengumuman: ' . $e->getMessage()
|
||||||
], 500);
|
], 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(['success' => true]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function details($id)
|
public function details($id)
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$announcement = Announcement::with('ruangans')->findOrFail($id);
|
$announcement = Announcement::with('ruangans')->findOrFail($id);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => [
|
||||||
'mode' => $announcement->mode,
|
'mode' => $announcement->mode,
|
||||||
'formatted_sent_at' => $announcement->formatted_sent_at,
|
'sent_at' => $announcement->sent_at->format('Y-m-d H:i:s'),
|
||||||
'message' => $announcement->message,
|
'message' => $announcement->message,
|
||||||
'ruangans' => $announcement->ruangans->map(function($ruangan) {
|
'ruangans' => $announcement->ruangans->map(function($ruangan) {
|
||||||
return ['nama_ruangan' => $ruangan->nama_ruangan];
|
return [
|
||||||
|
'id' => $ruangan->id,
|
||||||
|
'nama_ruangan' => $ruangan->nama_ruangan
|
||||||
|
];
|
||||||
})
|
})
|
||||||
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Failed to get announcement details: ' . $e->getMessage());
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Gagal mengambil detail pengumuman'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function destroy($id)
|
public function destroy($id)
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$announcement = Announcement::findOrFail($id);
|
$announcement = Announcement::findOrFail($id);
|
||||||
$announcement->delete();
|
$announcement->delete();
|
||||||
return response()->json(['success' => true]);
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Pengumuman berhasil dihapus'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Failed to delete announcement: ' . $e->getMessage());
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Gagal menghapus pengumuman'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkMqtt()
|
public function checkMqtt()
|
||||||
{
|
{
|
||||||
|
$mqttService = app(MqttService::class);
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'connected' => $this->checkMqttConnection()
|
'connected' => $mqttService->isConnected()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function controlRelay(Request $request)
|
public function controlRelay(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$validated = $request->validate([
|
||||||
'ruangans' => 'required|array|min:1',
|
'ruangans' => 'required|array|min:1',
|
||||||
'ruangans.*' => 'exists:ruangan,id',
|
'ruangans.*' => 'exists:ruangan,id',
|
||||||
'action' => 'required|in:activate,deactivate',
|
'action' => 'required|in:'.self::ACTION_ACTIVATE.','.self::ACTION_DEACTIVATE,
|
||||||
'mode' => 'required|in:manual,tts'
|
'mode' => 'required|in:'.self::MODE_MANUAL.','.self::MODE_TTS
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$ruangans = Ruangan::whereIn('id', $request->ruangans)->get();
|
DB::beginTransaction();
|
||||||
$state = $request->action === 'activate' ? 'on' : 'off';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Kirim perintah ke ESP32 via MQTT
|
$state = $validated['action'] === self::ACTION_ACTIVATE ? 'on' : 'off';
|
||||||
MQTT::publish('control/relay', json_encode([
|
$ruanganIds = $validated['ruangans'];
|
||||||
'action' => $request->action,
|
|
||||||
'ruang' => $request->ruangans,
|
|
||||||
'mode' => $request->mode
|
|
||||||
]));
|
|
||||||
|
|
||||||
// Update status relay di database
|
$mqttService = app(MqttService::class);
|
||||||
Ruangan::whereIn('id', $request->ruangans)->update(['relay_state' => $state]);
|
$success = $mqttService->sendRelayControl(
|
||||||
|
$validated['action'],
|
||||||
|
$ruanganIds,
|
||||||
|
$validated['mode']
|
||||||
|
);
|
||||||
|
|
||||||
// Jika mengaktifkan relay, simpan sebagai pengumuman manual
|
if (!$success) {
|
||||||
if ($request->action === 'activate' && $request->mode === 'manual') {
|
throw new \Exception('Gagal mengirim perintah ke perangkat');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update database
|
||||||
|
Ruangan::whereIn('id', $ruanganIds)->update(['relay_state' => $state]);
|
||||||
|
|
||||||
|
// Log manual activations as announcements
|
||||||
|
if ($validated['action'] === self::ACTION_ACTIVATE && $validated['mode'] === self::MODE_MANUAL) {
|
||||||
$announcement = Announcement::create([
|
$announcement = Announcement::create([
|
||||||
'mode' => 'manual',
|
'mode' => self::MODE_MANUAL,
|
||||||
'message' => 'Pengumuman via microphone manual',
|
'message' => 'Pengumuman via microphone manual',
|
||||||
'sent_at' => now()
|
'sent_at' => now()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$announcement->ruangans()->sync($request->ruangans);
|
$announcement->ruangans()->sync($ruanganIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(['success' => true]);
|
DB::commit();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Relay berhasil dikontrol'
|
||||||
|
]);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
Log::error('Failed to control relay: ' . $e->getMessage());
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
'message' => 'Gagal mengontrol relay: ' . $e->getMessage()
|
'message' => 'Gagal mengontrol relay: ' . $e->getMessage()
|
||||||
], 500);
|
], 500);
|
||||||
}
|
}
|
||||||
|
@ -155,7 +241,45 @@ public function controlRelay(Request $request)
|
||||||
|
|
||||||
public function relayStatus()
|
public function relayStatus()
|
||||||
{
|
{
|
||||||
$ruangans = Ruangan::select('id', 'relay_state')->get();
|
try {
|
||||||
return response()->json($ruangans);
|
$ruangans = Ruangan::select('id', 'nama_ruangan', 'relay_state')->get();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => $ruangans
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Failed to get relay status: ' . $e->getMessage());
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Gagal mengambil status relay'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function announcementStatus(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$request->validate([
|
||||||
|
'ruangans' => 'required|array|min:1',
|
||||||
|
'ruangans.*' => 'exists:ruangan,id'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$mqttService = app(MqttService::class);
|
||||||
|
$statuses = $mqttService->getAnnouncementStatus($request->ruangans);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => $statuses
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Failed to get announcement status: ' . $e->getMessage());
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Gagal mengambil status pengumuman'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -64,6 +64,19 @@ protected function handleBellEvent(string $message, string $triggerType): void
|
||||||
try {
|
try {
|
||||||
$data = json_decode($message, true, 512, JSON_THROW_ON_ERROR);
|
$data = json_decode($message, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
// Cek duplikasi dalam 60 detik terakhir
|
||||||
|
$existing = BellHistory::where('file_number', $data['file_number'])
|
||||||
|
->where('created_at', '>=', now()->subMinute())
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
Log::warning("Duplicate bell event blocked", [
|
||||||
|
'type' => $triggerType,
|
||||||
|
'data' => $data
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$requiredFields = ['hari', 'waktu', 'file_number'];
|
$requiredFields = ['hari', 'waktu', 'file_number'];
|
||||||
foreach ($requiredFields as $field) {
|
foreach ($requiredFields as $field) {
|
||||||
if (!isset($data[$field])) {
|
if (!isset($data[$field])) {
|
||||||
|
@ -97,16 +110,17 @@ protected function handleBellEvent(string $message, string $triggerType): void
|
||||||
|
|
||||||
private function normalizeWaktu(?string $time): string
|
private function normalizeWaktu(?string $time): string
|
||||||
{
|
{
|
||||||
if (empty($time)) {
|
if (empty($time)) return '00:00:00';
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Carbon::createFromFormat('H:i:s', $time)->format('H:i:s');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
try {
|
||||||
|
return Carbon::createFromFormat('H:i', $time)->format('H:i:s');
|
||||||
|
} catch (\Exception $e) {
|
||||||
return '00:00:00';
|
return '00:00:00';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
$parts = explode(':', $time);
|
|
||||||
$hour = min(23, max(0, (int)($parts[0] ?? 0)));
|
|
||||||
$minute = min(59, max(0, (int)($parts[1] ?? 0)));
|
|
||||||
$second = min(59, max(0, (int)($parts[2] ?? 0)));
|
|
||||||
|
|
||||||
return sprintf('%02d:%02d:%02d', $hour, $minute, $second);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function handleStatusResponse(string $message): void
|
protected function handleStatusResponse(string $message): void
|
||||||
|
@ -449,44 +463,43 @@ public function status()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getFormattedSchedules()
|
||||||
|
{
|
||||||
|
return JadwalBel::active()
|
||||||
|
->get()
|
||||||
|
->map(function ($item) {
|
||||||
|
return [
|
||||||
|
'hari' => $item->hari,
|
||||||
|
'waktu' => Carbon::parse($item->waktu)->format('H:i'), // Ensure "H:i" format
|
||||||
|
'file_number' => $item->file_number,
|
||||||
|
'volume' => (int)$item->volume ?? 15, // Force integer type
|
||||||
|
'repeat' => (int)$item->repeat ?? 1, // Force integer type
|
||||||
|
'is_active' => (bool)$item->is_active // Force boolean
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
public function syncSchedule()
|
public function syncSchedule()
|
||||||
{
|
{
|
||||||
try {
|
Log::debug('Sync request received from frontend');
|
||||||
$schedules = JadwalBel::active()
|
$payload = [
|
||||||
->get()
|
'action' => 'sync',
|
||||||
->map(fn($item) => [
|
'timestamp' => now()->toDateTimeString(),
|
||||||
'hari' => $item->hari,
|
'schedules' => $this->getFormattedSchedules()
|
||||||
'waktu' => Carbon::parse($item->waktu)->format('H:i'),
|
];
|
||||||
'file_number' => $item->file_number
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->mqttService->publish(
|
$this->mqttService->publish(
|
||||||
$this->mqttConfig['topics']['commands']['sync'],
|
'bel/sekolah/command/sync',
|
||||||
json_encode([
|
json_encode($payload),
|
||||||
'action' => 'sync',
|
1, // QoS 1
|
||||||
'timestamp' => Carbon::now()->toDateTimeString(),
|
false // Not retained
|
||||||
'schedules' => $schedules
|
|
||||||
]),
|
|
||||||
1
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Status::updateOrCreate(['id' => 1], ['last_sync' => Carbon::now()]);
|
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => 'Jadwal berhasil disinkronisasi',
|
'message' => 'Sync command sent',
|
||||||
'data' => [
|
'payload' => $payload // For debugging
|
||||||
'count' => $schedules->count(),
|
|
||||||
'last_sync' => Carbon::now()->toDateTimeString()
|
|
||||||
]
|
|
||||||
]);
|
]);
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('Gagal sync jadwal: ' . $e->getMessage());
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Gagal menyinkronisasi jadwal: ' . $e->getMessage()
|
|
||||||
], 500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function syncSchedules(): void
|
protected function syncSchedules(): void
|
||||||
|
@ -497,7 +510,10 @@ protected function syncSchedules(): void
|
||||||
->map(fn($item) => [
|
->map(fn($item) => [
|
||||||
'hari' => $item->hari,
|
'hari' => $item->hari,
|
||||||
'waktu' => Carbon::parse($item->waktu)->format('H:i:s'),
|
'waktu' => Carbon::parse($item->waktu)->format('H:i:s'),
|
||||||
'file_number' => $item->file_number
|
'file_number' => $item->file_number,
|
||||||
|
'volume' => $item->volume ?? 15, // Add this
|
||||||
|
'repeat' => $item->repeat ?? 1, // Add this
|
||||||
|
'is_active' => $item->is_active // Add this
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->mqttService->publish(
|
$this->mqttService->publish(
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use App\Models\BellHistory;
|
use App\Models\BellHistory;
|
||||||
use App\Events\BellRingEvent;
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
@ -22,6 +21,12 @@ class MqttService
|
||||||
protected const MAX_RECONNECT_ATTEMPTS = 5;
|
protected const MAX_RECONNECT_ATTEMPTS = 5;
|
||||||
protected const RECONNECT_DELAY = 5;
|
protected const RECONNECT_DELAY = 5;
|
||||||
|
|
||||||
|
// Constants for announcement topics
|
||||||
|
protected const TOPIC_ANNOUNCEMENT_CONTROL = 'control/relay';
|
||||||
|
protected const TOPIC_ANNOUNCEMENT_TTS = 'tts/play';
|
||||||
|
protected const TOPIC_ANNOUNCEMENT_STATUS = 'announcement/status';
|
||||||
|
protected const TOPIC_ANNOUNCEMENT_RESPONSE = 'announcement/response';
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->config = config('mqtt');
|
$this->config = config('mqtt');
|
||||||
|
@ -40,12 +45,23 @@ protected function initializeConnection(): void
|
||||||
|
|
||||||
$this->connect();
|
$this->connect();
|
||||||
$this->subscribeToBellTopics();
|
$this->subscribeToBellTopics();
|
||||||
|
$this->subscribeToAnnouncementTopics(); // Add announcement subscriptions
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error('MQTT Initialization failed: ' . $e->getMessage());
|
Log::error('MQTT Initialization failed: ' . $e->getMessage());
|
||||||
$this->scheduleReconnect();
|
$this->scheduleReconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In MqttService
|
||||||
|
public function getConnectionStatus(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'connected' => $this->isConnected,
|
||||||
|
'last_attempt' => Cache::get('mqtt_last_attempt'),
|
||||||
|
'queued_messages' => count(Cache::get('mqtt_message_queue', []))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
protected function getConnectionConfig(): array
|
protected function getConnectionConfig(): array
|
||||||
{
|
{
|
||||||
return $this->config['connections'][$this->config['default_connection']];
|
return $this->config['connections'][$this->config['default_connection']];
|
||||||
|
@ -68,6 +84,146 @@ protected function subscribeToBellTopics(): void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to announcement-related topics
|
||||||
|
*/
|
||||||
|
protected function subscribeToAnnouncementTopics(): void
|
||||||
|
{
|
||||||
|
$topics = [
|
||||||
|
self::TOPIC_ANNOUNCEMENT_STATUS => fn($t, $m) => $this->handleAnnouncementStatus($m),
|
||||||
|
self::TOPIC_ANNOUNCEMENT_RESPONSE => fn($t, $m) => $this->handleAnnouncementResponse($m)
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($topics as $topic => $callback) {
|
||||||
|
$this->subscribe($topic, $callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle announcement status updates from devices
|
||||||
|
*/
|
||||||
|
protected function handleAnnouncementStatus(string $message): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$data = json_decode($message, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
Log::info('Announcement status update', [
|
||||||
|
'status' => $data['status'] ?? 'unknown',
|
||||||
|
'ruang' => $data['ruang'] ?? null,
|
||||||
|
'mode' => $data['mode'] ?? null,
|
||||||
|
'timestamp' => $data['timestamp'] ?? null
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Store status in cache for quick access
|
||||||
|
if (isset($data['ruang'])) {
|
||||||
|
Cache::put('announcement_status_'.$data['ruang'], $data['status'], 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Failed to process announcement status', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'message' => $message
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle announcement responses from devices
|
||||||
|
*/
|
||||||
|
protected function handleAnnouncementResponse(string $message): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$data = json_decode($message, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
Log::debug('Announcement response received', [
|
||||||
|
'type' => $data['type'] ?? 'unknown',
|
||||||
|
'status' => $data['status'] ?? null,
|
||||||
|
'ruang' => $data['ruang'] ?? null,
|
||||||
|
'message' => $data['message'] ?? null
|
||||||
|
]);
|
||||||
|
|
||||||
|
// TODO: Add any specific response handling logic here
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Failed to process announcement response', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'message' => $message
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendRelayControl(string $action, array $ruangans, string $mode = 'manual'): bool
|
||||||
|
{
|
||||||
|
$payload = [
|
||||||
|
'action' => $action, // 'activate' atau 'deactivate'
|
||||||
|
'ruang' => $ruangans,
|
||||||
|
'mode' => $mode,
|
||||||
|
'timestamp' => now()->toDateTimeString()
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $this->publish(self::TOPIC_ANNOUNCEMENT_CONTROL, json_encode($payload));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Failed to send relay control', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'payload' => $payload
|
||||||
|
]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mengirim pengumuman TTS
|
||||||
|
*/
|
||||||
|
public function sendTTSAnnouncement(array $ruangans, string $message, string $language = 'id-id'): bool
|
||||||
|
{
|
||||||
|
$payload = [
|
||||||
|
'ruang' => $ruangans,
|
||||||
|
'teks' => $message,
|
||||||
|
'hl' => $language,
|
||||||
|
'timestamp' => now()->toDateTimeString()
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $this->publish(self::TOPIC_ANNOUNCEMENT_TTS, json_encode($payload));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Failed to send TTS announcement', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'payload' => $payload
|
||||||
|
]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fungsi kompatibilitas backward (bisa dihapus setelah update controller)
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
public function sendAnnouncement(string $mode, array $ruangans, ?string $message = null): bool
|
||||||
|
{
|
||||||
|
if ($mode === 'tts' && $message) {
|
||||||
|
return $this->sendTTSAnnouncement($ruangans, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default action untuk mode manual
|
||||||
|
return $this->sendRelayControl('activate', $ruangans, $mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current announcement status for rooms
|
||||||
|
*/
|
||||||
|
public function getAnnouncementStatus(array $ruanganIds): array
|
||||||
|
{
|
||||||
|
$statuses = [];
|
||||||
|
|
||||||
|
foreach ($ruanganIds as $id) {
|
||||||
|
$statuses[$id] = Cache::get('announcement_status_'.$id, 'unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $statuses;
|
||||||
|
}
|
||||||
|
|
||||||
protected function handleBellNotification(string $message, string $triggerType): void
|
protected function handleBellNotification(string $message, string $triggerType): void
|
||||||
{
|
{
|
||||||
Log::debug("Processing {$triggerType} bell event", compact('message', 'triggerType'));
|
Log::debug("Processing {$triggerType} bell event", compact('message', 'triggerType'));
|
||||||
|
@ -77,7 +233,11 @@ protected function handleBellNotification(string $message, string $triggerType):
|
||||||
$history = $this->createBellHistory($data, $triggerType);
|
$history = $this->createBellHistory($data, $triggerType);
|
||||||
|
|
||||||
$this->logBellEvent($history, $triggerType);
|
$this->logBellEvent($history, $triggerType);
|
||||||
$this->dispatchBellEvent($history);
|
|
||||||
|
Log::debug('Raw MQTT payload', [
|
||||||
|
'message' => $message,
|
||||||
|
'trigger_type' => $triggerType
|
||||||
|
]);
|
||||||
} catch (\JsonException $e) {
|
} catch (\JsonException $e) {
|
||||||
Log::error("Invalid JSON format in bell notification", [
|
Log::error("Invalid JSON format in bell notification", [
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
|
@ -144,17 +304,17 @@ protected function logBellEvent(BellHistory $history, string $triggerType): void
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function dispatchBellEvent(BellHistory $history): void
|
// protected function dispatchBellEvent(BellHistory $history): void
|
||||||
{
|
// {
|
||||||
event(new BellRingEvent([
|
// event(new BellRingEvent([
|
||||||
'id' => $history->id,
|
// 'id' => $history->id,
|
||||||
'hari' => $history->hari,
|
// 'hari' => $history->hari,
|
||||||
'waktu' => $history->waktu,
|
// 'waktu' => $history->waktu,
|
||||||
'file_number' => $history->file_number,
|
// 'file_number' => $history->file_number,
|
||||||
'trigger_type' => $history->trigger_type,
|
// 'trigger_type' => $history->trigger_type,
|
||||||
'ring_time' => $history->ring_time->toDateTimeString()
|
// 'ring_time' => $history->ring_time->toDateTimeString()
|
||||||
]));
|
// ]));
|
||||||
}
|
// }
|
||||||
|
|
||||||
private function normalizeTime(string $time): string
|
private function normalizeTime(string $time): string
|
||||||
{
|
{
|
||||||
|
@ -251,7 +411,7 @@ public function subscribe(string $topic, callable $callback, int $qos = 0): bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function publish(string $topic, string $message, int $qos = 0, bool $retain = false): bool
|
public function publish(string $topic, string $message, int $qos = 1, bool $retain = false): bool
|
||||||
{
|
{
|
||||||
if (!$this->isConnected && !$this->connect()) {
|
if (!$this->isConnected && !$this->connect()) {
|
||||||
$this->queueMessage($topic, $message, $qos, $retain);
|
$this->queueMessage($topic, $message, $qos, $retain);
|
||||||
|
|
|
@ -56,7 +56,7 @@ class="flex items-center px-5 py-2.5 bg-white border border-blue-500 text-blue-6
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<!-- Manual Mode Tab -->
|
<!-- Manual Mode Tab -->
|
||||||
<div id="manualTab" class="tab-content">
|
<div id="manualTab" class="tab-content">
|
||||||
<form id="manualForm" action="{{ route('admin.announcement.control-relay') }}" method="POST">
|
<form id="manualForm" action="{{ url('/api/announcements/relay/control') }}" method="POST">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="mode" value="manual">
|
<input type="hidden" name="mode" value="manual">
|
||||||
<input type="hidden" id="actionType" name="action" value="activate">
|
<input type="hidden" id="actionType" name="action" value="activate">
|
||||||
|
@ -100,10 +100,10 @@ class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
|
||||||
{{ $ruangan->relay_state === 'on' ? 'AKTIF' : 'NONAKTIF' }}
|
{{ $ruangan->relay_state === 'on' ? 'AKTIF' : 'NONAKTIF' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-gray-500 mt-1 flex items-center">
|
<!-- <p class="text-xs text-gray-500 mt-1 flex items-center">
|
||||||
<i class="fas fa-map-marker-alt mr-1 text-gray-400"></i>
|
<i class="fas fa-map-marker-alt mr-1 text-gray-400"></i>
|
||||||
{{ $ruangan->lokasi }}
|
{{ $ruangan->lokasi }}
|
||||||
</p>
|
</p> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
@ -126,7 +126,7 @@ class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
|
||||||
|
|
||||||
<!-- TTS Mode Tab -->
|
<!-- TTS Mode Tab -->
|
||||||
<div id="ttsTab" class="tab-content hidden">
|
<div id="ttsTab" class="tab-content hidden">
|
||||||
<form id="ttsForm" action="{{ route('admin.announcement.store') }}" method="POST">
|
<form id="ttsForm" action="{{ url('/api/announcements/store') }}" method="POST">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="mode" value="tts">
|
<input type="hidden" name="mode" value="tts">
|
||||||
|
|
||||||
|
@ -178,10 +178,10 @@ class="focus:ring-green-500 h-4 w-4 text-green-600 border-gray-300 rounded">
|
||||||
<span class="inline-block w-2.5 h-2.5 rounded-full bg-green-500 mr-2"></span>
|
<span class="inline-block w-2.5 h-2.5 rounded-full bg-green-500 mr-2"></span>
|
||||||
{{ $ruangan->nama_ruangan }}
|
{{ $ruangan->nama_ruangan }}
|
||||||
</label>
|
</label>
|
||||||
<p class="text-xs text-gray-500 mt-1 flex items-center">
|
<!-- <p class="text-xs text-gray-500 mt-1 flex items-center">
|
||||||
<i class="fas fa-map-marker-alt mr-1 text-gray-400"></i>
|
<i class="fas fa-map-marker-alt mr-1 text-gray-400"></i>
|
||||||
{{ $ruangan->lokasi }}
|
{{ $ruangan->lokasi }}
|
||||||
</p>
|
</p> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
@ -482,7 +482,7 @@ function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
|
||||||
|
|
||||||
// Check relay status periodically
|
// Check relay status periodically
|
||||||
function checkRelayStatus() {
|
function checkRelayStatus() {
|
||||||
$.get("{{ route('admin.announcement.relay-status') }}", function(data) {
|
$.get("{{ url('/api/announcements/relay/status') }}", function(data) {
|
||||||
data.forEach(room => {
|
data.forEach(room => {
|
||||||
const statusElement = $(`#status-ruang-${room.id}`);
|
const statusElement = $(`#status-ruang-${room.id}`);
|
||||||
if (statusElement.length) {
|
if (statusElement.length) {
|
||||||
|
@ -496,7 +496,7 @@ function checkRelayStatus() {
|
||||||
|
|
||||||
// Check MQTT connection status
|
// Check MQTT connection status
|
||||||
function checkMqttStatus() {
|
function checkMqttStatus() {
|
||||||
$.get("{{ route('admin.check.mqtt') }}", function(data) {
|
$.get("{{ url('/api/announcements/mqtt/status') }}", function(data) {
|
||||||
const statusElement = $('.mqtt-status-indicator');
|
const statusElement = $('.mqtt-status-indicator');
|
||||||
const textElement = $('.mqtt-status-text');
|
const textElement = $('.mqtt-status-text');
|
||||||
|
|
||||||
|
|
|
@ -274,7 +274,7 @@ class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ring bell function
|
// Ring bell function
|
||||||
function ringBell(fileNumber, volume = 20) {
|
function ringBell(fileNumber, volume = 15) {
|
||||||
showLoading('Ringing bell...');
|
showLoading('Ringing bell...');
|
||||||
fetch("{{ route('api.bel.ring') }}", {
|
fetch("{{ route('api.bel.ring') }}", {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
use App\Http\Controllers\PresensiController;
|
use App\Http\Controllers\PresensiController;
|
||||||
use App\Http\Controllers\SiswaController;
|
use App\Http\Controllers\SiswaController;
|
||||||
use App\Http\Controllers\API\BellController;
|
use App\Http\Controllers\API\BellController;
|
||||||
|
use App\Http\Controllers\AnnouncementController;
|
||||||
|
|
||||||
Route::prefix('bel')->group(function () {
|
Route::prefix('bel')->group(function () {
|
||||||
Route::post('/ring', [BelController::class, 'ring'])->name('api.bel.ring');
|
Route::post('/ring', [BelController::class, 'ring'])->name('api.bel.ring');
|
||||||
|
@ -18,9 +19,28 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// Endpoint untuk menerima data bel dari ESP32
|
// Endpoint untuk menerima data bel dari ESP32
|
||||||
Route::post('/bell-events', [BellController::class, 'storeScheduleEvent']);
|
Route::post('/bell-events/manual', [BellController::class, 'storeManualEvent']);
|
||||||
|
Route::post('/bell-events/schedule', [BellController::class, 'storeScheduleEvent']);
|
||||||
Route::get('/bell-history', [BellController::class, 'getHistory']);
|
Route::get('/bell-history', [BellController::class, 'getHistory']);
|
||||||
|
|
||||||
|
|
||||||
|
Route::prefix('announcements')->group(function () {
|
||||||
|
// CRUD operations
|
||||||
|
Route::post('/store', [AnnouncementController::class, 'store']);
|
||||||
|
Route::get('/{id}', [AnnouncementController::class, 'details']);
|
||||||
|
Route::delete('/{id}', [AnnouncementController::class, 'destroy']);
|
||||||
|
|
||||||
|
// Status checks
|
||||||
|
Route::get('/mqtt/status', [AnnouncementController::class, 'checkMqtt']);
|
||||||
|
|
||||||
|
// Relay control
|
||||||
|
Route::post('/relay/control', [AnnouncementController::class, 'controlRelay']);
|
||||||
|
Route::get('/relay/status', [AnnouncementController::class, 'relayStatus']);
|
||||||
|
|
||||||
|
// Announcement status
|
||||||
|
Route::post('/status', [AnnouncementController::class, 'announcementStatus']);
|
||||||
|
});
|
||||||
|
|
||||||
#Presensi
|
#Presensi
|
||||||
Route::post('/presensi', [PresensiController::class, 'store']);
|
Route::post('/presensi', [PresensiController::class, 'store']);
|
||||||
|
|
||||||
|
|
|
@ -106,19 +106,10 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Announcement System
|
|
||||||
// Announcement System
|
// Announcement System
|
||||||
Route::prefix('announcement')->controller(AnnouncementController::class)->group(function () {
|
Route::prefix('announcement')->controller(AnnouncementController::class)->group(function () {
|
||||||
Route::get('/', 'index')->name('admin.announcement.index');
|
Route::get('/', 'index')->name('admin.announcement.index');
|
||||||
Route::get('/history', 'history')->name('admin.announcement.history');
|
Route::get('/history', 'history')->name('admin.announcement.history');
|
||||||
Route::post('/', 'store')->name('admin.announcement.store');
|
|
||||||
Route::get('/{id}/details', 'details')->name('admin.announcement.details');
|
|
||||||
Route::delete('/{id}', 'destroy')->name('admin.announcement.destroy');
|
|
||||||
|
|
||||||
// MQTT & Relay
|
|
||||||
Route::get('/check/mqtt', 'checkMqtt')->name('admin.check.mqtt');
|
|
||||||
Route::post('/control-relay', 'controlRelay')->name('admin.announcement.control-relay');
|
|
||||||
Route::get('/relay-status', 'relayStatus')->name('admin.announcement.relay-status');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue