fix all pengumuman

This commit is contained in:
rendygaafk 2025-05-11 06:51:06 +07:00
parent b43c74e5ad
commit 4448c83266
9 changed files with 543 additions and 198 deletions

View File

@ -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');
}
}

View File

@ -5,46 +5,100 @@
use App\Http\Controllers\Controller;
use App\Models\BellHistory;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
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)
{
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([
'hari' => 'required|in:Senin,Selasa,Rabu,Kamis,Jumat,Sabtu,Minggu',
'waktu' => 'required|date_format:H:i:s',
'file_number' => 'required|string|size:4',
'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
$validated['trigger_type'] = 'schedule';
$validated['ring_time'] = now();
// Set default values jika tidak ada
$validated['volume'] = $validated['volume'] ?? 15;
$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);
return response()->json([
'success' => true,
'message' => 'Bell event recorded successfully',
'data' => $history
], 201);
}
/**
* Mengambil data history untuk ESP32 (jika diperlukan)
* Mengambil data history untuk ESP32
*/
public function getHistory(Request $request)
{
$histories = BellHistory::orderBy('ring_time', 'desc')
->limit(50)
->get();
$request->validate([
'limit' => 'sometimes|integer|min:1|max:100',
'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([
'success' => true,
'count' => $histories->count(),
'data' => $histories
]);
}
}
}

View File

@ -6,148 +6,234 @@
use App\Models\Ruangan;
use App\Http\Requests\StoreAnnouncementRequest;
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
{
// Constants for modes and actions
const MODE_TTS = 'tts';
const MODE_MANUAL = 'manual';
const ACTION_ACTIVATE = 'activate';
const ACTION_DEACTIVATE = 'deactivate';
public function index()
{
$ruangans = Ruangan::orderBy('nama_ruangan')->get();
$mqttStatus = $this->checkMqttConnection();
return view('admin.announcement.index', compact('ruangans', 'mqttStatus'));
try {
$ruangans = Ruangan::orderBy('nama_ruangan')->get();
$mqttService = app(MqttService::class);
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()
{
$announcements = Announcement::with('ruangans')
->when(request('mode'), function($query, $mode) {
return $query->where('mode', $mode);
})
->when(request('date'), function($query, $date) {
return $query->whereDate('sent_at', $date);
})
->orderBy('sent_at', 'desc')
->paginate(10);
return view('admin.announcement.history', compact('announcements'));
}
private function checkMqttConnection()
{
try {
$mqtt = MQTT::connection();
return $mqtt->isConnected();
$announcements = Announcement::with('ruangans')
->when(request('mode'), function($query, $mode) {
return $query->where('mode', $mode);
})
->when(request('date'), function($query, $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')
->paginate(10)
->withQueryString();
$ruangans = Ruangan::orderBy('nama_ruangan')->get();
return view('admin.announcement.history', [
'announcements' => $announcements,
'ruangans' => $ruangans,
'modes' => [self::MODE_TTS, self::MODE_MANUAL]
]);
} 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)
{
$announcementData = [
'mode' => $request->mode,
'sent_at' => now(),
];
// Hanya tambahkan message jika mode TTS
if ($request->mode === 'tts') {
$announcementData['message'] = $request->message;
}
$announcement = Announcement::create($announcementData);
$announcement->ruangans()->sync($request->ruangans);
DB::beginTransaction();
try {
if ($request->mode === 'tts') {
MQTT::publish('control/relay', json_encode([
'mode' => 'tts',
'ruang' => $request->ruangans
]));
MQTT::publish('tts/play', json_encode([
'ruang' => $request->ruangans,
'teks' => $request->message
]));
} else {
MQTT::publish('control/relay', json_encode([
'mode' => 'reguler',
'ruang' => $request->ruangans
]));
$announcementData = [
'mode' => $request->mode,
'sent_at' => now(),
];
if ($request->mode === self::MODE_TTS) {
$announcementData['message'] = $request->message;
}
} catch (\Exception $e) {
$announcement = Announcement::create($announcementData);
$announcement->ruangans()->sync($request->ruangans);
$mqttService = app(MqttService::class);
if ($request->mode === self::MODE_TTS) {
$success = $mqttService->sendTTSAnnouncement(
$request->ruangans,
$request->message
);
} else {
$success = $mqttService->sendRelayControl(
'activate', // Default action for announcements
$request->ruangans,
$request->mode
);
}
if (!$success) {
throw new \Exception('Gagal mengirim perintah ke perangkat');
}
DB::commit();
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);
}
return response()->json(['success' => true]);
}
public function details($id)
public function details($id)
{
$announcement = Announcement::with('ruangans')->findOrFail($id);
return response()->json([
'mode' => $announcement->mode,
'formatted_sent_at' => $announcement->formatted_sent_at,
'message' => $announcement->message,
'ruangans' => $announcement->ruangans->map(function($ruangan) {
return ['nama_ruangan' => $ruangan->nama_ruangan];
})
]);
try {
$announcement = Announcement::with('ruangans')->findOrFail($id);
return response()->json([
'success' => true,
'data' => [
'mode' => $announcement->mode,
'sent_at' => $announcement->sent_at->format('Y-m-d H:i:s'),
'message' => $announcement->message,
'ruangans' => $announcement->ruangans->map(function($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)
{
$announcement = Announcement::findOrFail($id);
$announcement->delete();
return response()->json(['success' => true]);
try {
$announcement = Announcement::findOrFail($id);
$announcement->delete();
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()
{
$mqttService = app(MqttService::class);
return response()->json([
'connected' => $this->checkMqttConnection()
'connected' => $mqttService->isConnected()
]);
}
public function controlRelay(Request $request)
{
$request->validate([
$validated = $request->validate([
'ruangans' => 'required|array|min:1',
'ruangans.*' => 'exists:ruangan,id',
'action' => 'required|in:activate,deactivate',
'mode' => 'required|in:manual,tts'
'action' => 'required|in:'.self::ACTION_ACTIVATE.','.self::ACTION_DEACTIVATE,
'mode' => 'required|in:'.self::MODE_MANUAL.','.self::MODE_TTS
]);
$ruangans = Ruangan::whereIn('id', $request->ruangans)->get();
$state = $request->action === 'activate' ? 'on' : 'off';
DB::beginTransaction();
try {
// Kirim perintah ke ESP32 via MQTT
MQTT::publish('control/relay', json_encode([
'action' => $request->action,
'ruang' => $request->ruangans,
'mode' => $request->mode
]));
$state = $validated['action'] === self::ACTION_ACTIVATE ? 'on' : 'off';
$ruanganIds = $validated['ruangans'];
// Update status relay di database
Ruangan::whereIn('id', $request->ruangans)->update(['relay_state' => $state]);
$mqttService = app(MqttService::class);
$success = $mqttService->sendRelayControl(
$validated['action'],
$ruanganIds,
$validated['mode']
);
// Jika mengaktifkan relay, simpan sebagai pengumuman manual
if ($request->action === 'activate' && $request->mode === 'manual') {
if (!$success) {
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([
'mode' => 'manual',
'mode' => self::MODE_MANUAL,
'message' => 'Pengumuman via microphone manual',
'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) {
DB::rollBack();
Log::error('Failed to control relay: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Gagal mengontrol relay: ' . $e->getMessage()
], 500);
}
@ -155,7 +241,45 @@ public function controlRelay(Request $request)
public function relayStatus()
{
$ruangans = Ruangan::select('id', 'relay_state')->get();
return response()->json($ruangans);
try {
$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);
}
}
}

View File

@ -64,6 +64,19 @@ protected function handleBellEvent(string $message, string $triggerType): void
try {
$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'];
foreach ($requiredFields as $field) {
if (!isset($data[$field])) {
@ -97,16 +110,17 @@ protected function handleBellEvent(string $message, string $triggerType): void
private function normalizeWaktu(?string $time): string
{
if (empty($time)) {
return '00:00:00';
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';
}
}
$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
@ -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()
{
try {
$schedules = JadwalBel::active()
->get()
->map(fn($item) => [
'hari' => $item->hari,
'waktu' => Carbon::parse($item->waktu)->format('H:i'),
'file_number' => $item->file_number
]);
$this->mqttService->publish(
$this->mqttConfig['topics']['commands']['sync'],
json_encode([
'action' => 'sync',
'timestamp' => Carbon::now()->toDateTimeString(),
'schedules' => $schedules
]),
1
);
Status::updateOrCreate(['id' => 1], ['last_sync' => Carbon::now()]);
return response()->json([
'success' => true,
'message' => 'Jadwal berhasil disinkronisasi',
'data' => [
'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);
}
Log::debug('Sync request received from frontend');
$payload = [
'action' => 'sync',
'timestamp' => now()->toDateTimeString(),
'schedules' => $this->getFormattedSchedules()
];
$this->mqttService->publish(
'bel/sekolah/command/sync',
json_encode($payload),
1, // QoS 1
false // Not retained
);
return response()->json([
'success' => true,
'message' => 'Sync command sent',
'payload' => $payload // For debugging
]);
}
protected function syncSchedules(): void
@ -497,7 +510,10 @@ protected function syncSchedules(): void
->map(fn($item) => [
'hari' => $item->hari,
'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(

View File

@ -7,7 +7,6 @@
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
use App\Models\BellHistory;
use App\Events\BellRingEvent;
use Carbon\Carbon;
use Illuminate\Support\Facades\Validator;
@ -22,6 +21,12 @@ class MqttService
protected const MAX_RECONNECT_ATTEMPTS = 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()
{
$this->config = config('mqtt');
@ -40,12 +45,23 @@ protected function initializeConnection(): void
$this->connect();
$this->subscribeToBellTopics();
$this->subscribeToAnnouncementTopics(); // Add announcement subscriptions
} catch (\Exception $e) {
Log::error('MQTT Initialization failed: ' . $e->getMessage());
$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
{
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
{
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);
$this->logBellEvent($history, $triggerType);
$this->dispatchBellEvent($history);
Log::debug('Raw MQTT payload', [
'message' => $message,
'trigger_type' => $triggerType
]);
} catch (\JsonException $e) {
Log::error("Invalid JSON format in bell notification", [
'error' => $e->getMessage(),
@ -144,17 +304,17 @@ protected function logBellEvent(BellHistory $history, string $triggerType): void
]);
}
protected function dispatchBellEvent(BellHistory $history): void
{
event(new BellRingEvent([
'id' => $history->id,
'hari' => $history->hari,
'waktu' => $history->waktu,
'file_number' => $history->file_number,
'trigger_type' => $history->trigger_type,
'ring_time' => $history->ring_time->toDateTimeString()
]));
}
// protected function dispatchBellEvent(BellHistory $history): void
// {
// event(new BellRingEvent([
// 'id' => $history->id,
// 'hari' => $history->hari,
// 'waktu' => $history->waktu,
// 'file_number' => $history->file_number,
// 'trigger_type' => $history->trigger_type,
// 'ring_time' => $history->ring_time->toDateTimeString()
// ]));
// }
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()) {
$this->queueMessage($topic, $message, $qos, $retain);

View File

@ -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">
<!-- Manual Mode Tab -->
<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
<input type="hidden" name="mode" value="manual">
<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' }}
</span>
</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>
{{ $ruangan->lokasi }}
</p>
</p> -->
</div>
</div>
@endforeach
@ -126,7 +126,7 @@ class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
<!-- TTS Mode Tab -->
<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
<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>
{{ $ruangan->nama_ruangan }}
</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>
{{ $ruangan->lokasi }}
</p>
</p> -->
</div>
</div>
@endforeach
@ -482,7 +482,7 @@ function switchTab(activeTab, inactiveTab, activeBtn, inactiveBtn) {
// Check relay status periodically
function checkRelayStatus() {
$.get("{{ route('admin.announcement.relay-status') }}", function(data) {
$.get("{{ url('/api/announcements/relay/status') }}", function(data) {
data.forEach(room => {
const statusElement = $(`#status-ruang-${room.id}`);
if (statusElement.length) {
@ -496,7 +496,7 @@ function checkRelayStatus() {
// Check MQTT connection status
function checkMqttStatus() {
$.get("{{ route('admin.check.mqtt') }}", function(data) {
$.get("{{ url('/api/announcements/mqtt/status') }}", function(data) {
const statusElement = $('.mqtt-status-indicator');
const textElement = $('.mqtt-status-text');

View File

@ -274,7 +274,7 @@ class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-
}
// Ring bell function
function ringBell(fileNumber, volume = 20) {
function ringBell(fileNumber, volume = 15) {
showLoading('Ringing bell...');
fetch("{{ route('api.bel.ring') }}", {
method: 'POST',

View File

@ -5,6 +5,7 @@
use App\Http\Controllers\PresensiController;
use App\Http\Controllers\SiswaController;
use App\Http\Controllers\API\BellController;
use App\Http\Controllers\AnnouncementController;
Route::prefix('bel')->group(function () {
Route::post('/ring', [BelController::class, 'ring'])->name('api.bel.ring');
@ -18,9 +19,28 @@
});
// 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::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
Route::post('/presensi', [PresensiController::class, 'store']);

View File

@ -106,19 +106,10 @@
});
});
// Announcement System
// Announcement System
Route::prefix('announcement')->controller(AnnouncementController::class)->group(function () {
Route::get('/', 'index')->name('admin.announcement.index');
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');
});
});