| @@ -13,7 +13,6 @@ import { MatDialog } from '@angular/material/dialog'; | |||
| import { AlarmSoundService } from '../../../shared/services/alarm-sound.service'; | |||
| import { CameraDialogComponent } from '../../../shared/component/camera-dialog/camera-dialog.component'; | |||
| import { alarmData, alarmDemo } from '../data/fake-data'; | |||
| import { ConfirmDialogService } from '../../../shared/services/confirm-dialog.service'; | |||
| @Component({ | |||
| selector: 'app-centralized-security-management', | |||
| @@ -30,7 +29,9 @@ export class CentralizedSecurityManagementComponent | |||
| data = alarmData; | |||
| alarmDemo = alarmDemo; | |||
| state1 = false; | |||
| status1 = false; | |||
| state2 = false; | |||
| status2 = false; | |||
| state5 = false; | |||
| state6 = false; | |||
| isReady = true; | |||
| @@ -99,12 +100,13 @@ export class CentralizedSecurityManagementComponent | |||
| onMessage(message: any) { | |||
| if (message.id == '0' && message.type === 'get') { | |||
| this.state1 = message.state1 === '0'; // 1 OFF // alarm 12h | |||
| this.status1 = message.status1 === '1'; // 0 not, 1 ready, 2 error, 3 bypass | |||
| this.state2 = message.state2 === '0'; // 1 OFF // alarm 1h | |||
| this.status2 = message.status2 === '1'; // 0 not, 1 ready, 2 error, 3 bypass | |||
| this.state5 = message.state5 === '1'; // 1 ON, 0 OFF | |||
| this.isReady = message.ready === '1'; | |||
| this.alarmSoundService$.startAlarm(this.state5, this.isReady, this.state1, this.state2); | |||
| this.alarmSoundService$.startAlarm(this.state5, this.state1,this.status1, this.state2, this.status2); | |||
| this.updateIcons(); | |||
| } | |||
| } | |||
| @@ -117,8 +119,9 @@ export class CentralizedSecurityManagementComponent | |||
| } | |||
| addMarker(item: any, isDemo: boolean = false): void { | |||
| console.log(this.state5, this.state1, this.status1) | |||
| const icon = isDemo | |||
| ? this.getIcon(this.state5, this.isReady, this.state1, this.state2) | |||
| ? this.getIcon(this.state5, this.state1, this.status1, this.state2, this.status2) | |||
| : this.createIcon(item.warning); | |||
| const marker = L.marker( | |||
| [item.detail.coordinates.lat, item.detail.coordinates.lng], | |||
| @@ -164,11 +167,7 @@ export class CentralizedSecurityManagementComponent | |||
| </div>`; | |||
| } | |||
| createIcon( | |||
| active: boolean, | |||
| className: string = '', | |||
| text: any = [], | |||
| ): L.Icon<L.IconOptions> | L.DivIcon { | |||
| createIcon(active: boolean, className: string = '', text: any = []): L.Icon<L.IconOptions> | L.DivIcon { | |||
| if (text.length < 1) { | |||
| return L.icon({ | |||
| iconUrl: active | |||
| @@ -198,22 +197,17 @@ export class CentralizedSecurityManagementComponent | |||
| } | |||
| } | |||
| getIcon( | |||
| isTurnOn: boolean, | |||
| isReady: boolean, | |||
| fireArm: boolean, | |||
| fenceArm: boolean, | |||
| ): L.Icon<L.IconOptions> | L.DivIcon { | |||
| if (isTurnOn && isReady) { | |||
| getIcon(isTurnOn: boolean, fireArm: boolean, fireStatus: boolean, fenceArm: boolean, fenceStatus: boolean): L.Icon<L.IconOptions> | L.DivIcon { | |||
| if (isTurnOn) { | |||
| let text = []; | |||
| if (fireArm && fenceArm) { | |||
| text.push('FIRE ALARM', 'FENCE ALARM'); | |||
| } else if (fireArm) { | |||
| if (fireStatus && fireArm) { | |||
| text.push('FIRE ALARM'); | |||
| } else if (fenceArm) { | |||
| } | |||
| if (fenceStatus && fenceArm) { | |||
| text.push('FENCE ALARM'); | |||
| } | |||
| return this.createIcon(true, '', text); | |||
| return this.createIcon(true, '', text); | |||
| } | |||
| return this.createIcon(false); | |||
| } | |||
| @@ -7,7 +7,6 @@ | |||
| </button> | |||
| </div> | |||
| <mat-card class="sound-group"> | |||
| <mat-card-header> | |||
| <mat-card-title>WHISTLE TIME: {{whistle.time}}s</mat-card-title> | |||
| @@ -17,13 +16,13 @@ | |||
| <app-slider-range | |||
| [value]="whistle.time" | |||
| [icon]="'access_alarm'" | |||
| (valueChange)="whistle.time = $event" | |||
| (valueChange)="emitSetting('whistle', $event)" | |||
| [disable]="!isConnected" | |||
| ></app-slider-range> | |||
| </div> | |||
| </mat-card-content> | |||
| </mat-card> | |||
| <mat-card class="sound-group"> | |||
| <mat-card-header> | |||
| <mat-card-title>24-HOUR ZONE ALARM TIME: {{alarm.time}}s</mat-card-title> | |||
| @@ -33,9 +32,9 @@ | |||
| <app-slider-range | |||
| [value]="alarm.time" | |||
| [icon]="'access_alarm'" | |||
| (valueChange)="alarm.time = $event" | |||
| (valueChange)="emitSetting('alarm',$event)" | |||
| [disable]="!isConnected" | |||
| ></app-slider-range> | |||
| </div> | |||
| </mat-card-content> | |||
| </mat-card> | |||
| @@ -1,11 +1,13 @@ | |||
| import { Component } from '@angular/core'; | |||
| import {Component, OnDestroy, OnInit} from '@angular/core'; | |||
| import {SocketService} from "../../../shared/services/socket.service"; | |||
| import {Subscription} from "rxjs"; | |||
| @Component({ | |||
| selector: 'app-homepage', | |||
| templateUrl: './home-page.component.html', | |||
| styleUrls: ['./home-page.component.scss'], | |||
| }) | |||
| export class HomePageComponent { | |||
| export class HomePageComponent implements OnInit,OnDestroy{ | |||
| whistle = { | |||
| time: 10, | |||
| sound: 0, | |||
| @@ -14,4 +16,43 @@ export class HomePageComponent { | |||
| time: 30, | |||
| sound: 0, | |||
| }; | |||
| private statusSubscription?: Subscription; | |||
| isConnected: boolean = false; | |||
| constructor(private socketService$: SocketService,) { | |||
| this.statusSubscription = this.socketService$.status$.subscribe( | |||
| (isConnected) => { | |||
| this.isConnected = isConnected; | |||
| }, | |||
| ); | |||
| } | |||
| ngOnInit() { | |||
| this.whistle.time = parseInt(localStorage.getItem('whistletime') ?? '10'); | |||
| if (!localStorage.getItem('whistletime')) { | |||
| localStorage.setItem('whistletime', '10'); | |||
| } | |||
| this.alarm.time = parseInt(localStorage.getItem('alarmtime') ?? '30'); | |||
| if (!localStorage.getItem('alarmtime')) { | |||
| localStorage.setItem('alarmtime', '30'); | |||
| } | |||
| } | |||
| emitSetting(type: string, event: any){ | |||
| if (this.isConnected) { | |||
| if (type === 'whistle') { | |||
| this.whistle.time = event; | |||
| localStorage.setItem('whistletime', event); | |||
| this.socketService$.sendMessage({ id: '0', type: 'set', whistletime: event.toString() }); | |||
| } else if (type === 'alarm') { | |||
| this.alarm.time = event; | |||
| localStorage.setItem('alarmtime', event); | |||
| this.socketService$.sendMessage({ id: '0', type: 'set', arlamtime: event.toString() }); | |||
| } | |||
| } | |||
| } | |||
| ngOnDestroy(): void { | |||
| if (this.statusSubscription) { | |||
| this.statusSubscription.unsubscribe(); | |||
| } | |||
| } | |||
| } | |||
| @@ -5,6 +5,7 @@ import {ToastrService} from "ngx-toastr"; | |||
| import {CameraDialogComponent} from "../../../shared/component/camera-dialog/camera-dialog.component"; | |||
| import {MatDialog} from "@angular/material/dialog"; | |||
| import { ConfirmDialogService } from '../../../shared/services/confirm-dialog.service'; | |||
| import {AlarmSoundService} from "../../../shared/services/alarm-sound.service"; | |||
| @Component({ | |||
| selector: 'app-security-system-details', | |||
| @@ -31,9 +32,9 @@ export class SecuritySystemDetailsComponent implements OnInit, OnDestroy { | |||
| constructor( | |||
| private socketService$: SocketService, | |||
| private toastr: ToastrService, | |||
| private dialog: MatDialog, | |||
| private confirm$: ConfirmDialogService | |||
| private confirm$: ConfirmDialogService, | |||
| private alarmSoundService$: AlarmSoundService, | |||
| ) {} | |||
| ngOnInit() { | |||
| @@ -61,6 +62,7 @@ export class SecuritySystemDetailsComponent implements OnInit, OnDestroy { | |||
| clearInterval(this.intervalId); | |||
| } | |||
| this.socketService$.close(); | |||
| this.alarmSoundService$.stopAlarm(); | |||
| } | |||
| getReadings() { | |||
| @@ -104,6 +106,7 @@ export class SecuritySystemDetailsComponent implements OnInit, OnDestroy { | |||
| this.confirm$.openDialog(data); | |||
| // this.toastr.warning('System not ready', 'Warning', {timeOut: 5000}); | |||
| } | |||
| this.alarmSoundService$.startAlarm(this.state5, this.state1,this.status1, this.state2, this.status2); | |||
| } | |||
| } | |||
| openDialog(): void { | |||
| @@ -1,11 +1,16 @@ | |||
| <div style="padding: 10px 20px" fxLayout="row" fxLayoutAlign="center"> | |||
| <div style="text-align: center; font-size: 24px; width: 95%">Camera Stream</div> | |||
| <button mat-icon-button color="warn" (click)="onClose()"> | |||
| <mat-icon>close</mat-icon> | |||
| </button> | |||
| </div> | |||
| <div style="padding: 10px 20px" fxLayout="row" fxLayoutAlign=" center"> | |||
| <div style="text-align: center; font-size: 24px; width: 95%">Camera Stream</div> | |||
| <button mat-icon-button color="warn" (click)="onClose()"> | |||
| <mat-icon>close</mat-icon> | |||
| </button> | |||
| <div mat-dialog-content> | |||
| <div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="10px" | |||
| style="height: 50%;" | |||
| *ngIf="!videoLoaded"> | |||
| <div class="circle-loader"></div> | |||
| <div class="loader"></div> | |||
| </div> | |||
| <div mat-dialog-content> | |||
| <canvas class="video" #videoPlayer></canvas> | |||
| </div> | |||
| <canvas class="video" #videoPlayer [hidden]="!videoLoaded"></canvas> | |||
| </div> | |||
| @@ -7,3 +7,30 @@ | |||
| .mat-mdc-dialog-content{ | |||
| max-height: unset; | |||
| } | |||
| .loader { | |||
| width: fit-content; | |||
| color: orange; | |||
| font-family: monospace; | |||
| font-size: 30px; | |||
| clip-path: inset(0 3ch 0 0); | |||
| animation: l4 1s steps(4) infinite; | |||
| } | |||
| .loader:before { | |||
| content:"Loading..." | |||
| } | |||
| @keyframes l4 {to{clip-path: inset(0 -1ch 0 0)}} | |||
| .circle-loader { | |||
| width: 50px; | |||
| aspect-ratio: 1; | |||
| border-radius: 50%; | |||
| background: | |||
| radial-gradient(farthest-side,#ffa516 94%,#0000) top/8px 8px no-repeat, | |||
| conic-gradient(#0000 30%,#ffa516); | |||
| -webkit-mask: radial-gradient(farthest-side,#0000 calc(100% - 8px),#000 0); | |||
| animation: l13 1s infinite linear; | |||
| } | |||
| @keyframes l13{ | |||
| 100%{transform: rotate(1turn)} | |||
| } | |||
| @@ -10,9 +10,8 @@ import {loadPlayer, Player} from "rtsp-relay/browser"; | |||
| export class CameraDialogComponent implements AfterViewInit{ | |||
| player?: Player; | |||
| @ViewChild('videoPlayer') | |||
| videoPlayer?: ElementRef<HTMLCanvasElement>; | |||
| videoLoaded: boolean = false; | |||
| @ViewChild('videoPlayer') videoPlayer?: ElementRef<HTMLCanvasElement>; | |||
| constructor(public dialogRef: MatDialogRef<CameraDialogComponent>) { } | |||
| @@ -22,9 +21,10 @@ export class CameraDialogComponent implements AfterViewInit{ | |||
| url: 'ws://localhost:8080/stream', | |||
| canvas: this.videoPlayer!.nativeElement, | |||
| onDisconnect: () => { | |||
| setTimeout(connect, 5000); // reconnect after 5 seconds | |||
| setTimeout(connect, 5000); | |||
| }, | |||
| }); | |||
| this.videoLoaded = true; | |||
| }; | |||
| connect(); | |||
| } | |||
| @@ -1,11 +1,15 @@ | |||
| <h5 mat-dialog-title >CONFIRM TO IGNORE THE WARNING</h5> | |||
| <mat-dialog-content style="padding-bottom: 4px"> | |||
| <div *ngFor="let item of data"> | |||
| <mat-checkbox [(ngModel)]="item.value" (ngModelChange)="submitChange(item)" name="sensor"> | |||
| {{ (item.key == 'status1' ? 'Fire' : 'ff')}} | |||
| </mat-checkbox> | |||
| </div> | |||
| <div *ngFor="let item of data"> | |||
| <mat-checkbox [(ngModel)]="item.value" | |||
| (ngModelChange)="submitChange(item)" | |||
| [disabled]="isConnected && item.value" | |||
| name="sensor"> | |||
| {{ (item.key == 'status1' ? 'Fire Alarm' : 'Fence Alarm') }} | |||
| </mat-checkbox> | |||
| </div> | |||
| </mat-dialog-content> | |||
| <mat-dialog-actions align="end"> | |||
| <button mat-button mat-dialog-close cdkFocusInitial>Close</button> | |||
| </mat-dialog-actions> | |||
| @@ -1,25 +1,40 @@ | |||
| import { Component } from '@angular/core'; | |||
| import { MatDialogRef } from '@angular/material/dialog'; | |||
| import {Component, Inject, OnDestroy, OnInit} from '@angular/core'; | |||
| import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; | |||
| import {SocketService} from "../../services/socket.service"; | |||
| import {Subscription} from "rxjs"; | |||
| @Component({ | |||
| selector: 'app-confirm-dialog', | |||
| templateUrl: './confirm-dialog.component.html', | |||
| styleUrls: ['./confirm-dialog.component.scss'], | |||
| }) | |||
| export class ConfirmDialogComponent { | |||
| constructor(public dialogRef: MatDialogRef<ConfirmDialogComponent>) {} | |||
| data = [ | |||
| { | |||
| key: 'status1', | |||
| value: false, | |||
| export class ConfirmDialogComponent implements OnDestroy{ | |||
| private statusSubscription?: Subscription; | |||
| isConnected: boolean = false; | |||
| constructor( | |||
| @Inject(MAT_DIALOG_DATA) public data: any[], | |||
| private socketService$: SocketService, | |||
| ) { | |||
| this.statusSubscription = this.socketService$.status$.subscribe( | |||
| (isConnected) => { | |||
| this.isConnected = isConnected; | |||
| }, | |||
| { | |||
| name: 'status2', | |||
| value: false, | |||
| }, | |||
| ]; | |||
| ); | |||
| } | |||
| submitChange(item: any): void{ | |||
| console.log(item); | |||
| // call socket here | |||
| if(this.isConnected) { | |||
| const position = item.key === 'status1' ? 1 : 2; | |||
| this.socketService$.sendMessage({ id: '0', type: 'bypass', state: position}) | |||
| item.value = true; | |||
| } | |||
| } | |||
| ngOnDestroy() { | |||
| if (this.statusSubscription) { | |||
| this.statusSubscription.unsubscribe(); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,9 +1,9 @@ | |||
| <ng-content></ng-content> | |||
| <div class="volume-group"> | |||
| <div class="speaker"> | |||
| <mat-icon>{{icon}}</mat-icon> | |||
| <mat-icon>{{ icon }}</mat-icon> | |||
| </div> | |||
| <button class="volume-control" (click)="decreaseVolume()"> | |||
| <button class="volume-control" [disabled]="disable" (click)="decreaseVolume()"> | |||
| <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" | |||
| xmlns:xlink="http://www.w3.org/1999/xlink"> | |||
| <title>Icons/Minus square fill</title> | |||
| @@ -21,10 +21,11 @@ | |||
| <div class="volume-slider"> | |||
| <input type="range" [(ngModel)]="value" (input)="onSliderChange($event)" | |||
| [ngStyle]="onSliderChangeBackground()" | |||
| [max]="max"> | |||
| [max]="max" | |||
| [disabled]="disable"> | |||
| </div> | |||
| </div> | |||
| <button class="volume-control" (click)="increaseVolume()"> | |||
| <button class="volume-control" [disabled]="disable" (click)="increaseVolume()"> | |||
| <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" | |||
| xmlns:xlink="http://www.w3.org/1999/xlink"> | |||
| <title>Icons/Plus square fill</title> | |||
| @@ -1,4 +1,5 @@ | |||
| import { Injectable } from '@angular/core'; | |||
| import {max} from "rxjs"; | |||
| @Injectable({ | |||
| providedIn: 'root', | |||
| @@ -7,21 +8,18 @@ export class AlarmSoundService { | |||
| private alertInterval: any; | |||
| private alertDuration: number = 10000; | |||
| private audio = new Audio(); | |||
| private isPlaying: boolean = false; | |||
| private isPlay: boolean = false; | |||
| constructor() { | |||
| this.audio.src = 'assets/sound/alarm_5m.mp3'; | |||
| this.audio.load(); | |||
| this.audio.onended = () => { | |||
| // Khi phát hết âm thanh, đặt biến isPlaying về false | |||
| this.isPlaying = false; | |||
| }; | |||
| } | |||
| playSound(): void { | |||
| if (!this.isPlaying) { | |||
| if(!this.isPlay){ | |||
| this.audio.play().then(() => { | |||
| this.isPlaying = true; | |||
| this.isPlay = true; | |||
| this.alertInterval = setInterval(() => { | |||
| this.stopAlarm(); | |||
| }, this.alertDuration); | |||
| @@ -29,30 +27,45 @@ export class AlarmSoundService { | |||
| console.error('Error playing audio:', error); | |||
| }); | |||
| } | |||
| } | |||
| stopSound(): void { | |||
| if (this.audio) { | |||
| this.audio.pause(); | |||
| this.audio.currentTime = 0; | |||
| this.isPlaying = false; | |||
| startAlarm(isTurnOn: boolean, fireArm: boolean, fireStatus: boolean, fenceArm: boolean, fenceStatus: boolean): void { | |||
| if (!isTurnOn) { | |||
| this.stopAlarm(); | |||
| return; | |||
| } | |||
| } | |||
| startAlarm( isTurnOn: boolean, isReady: boolean, fireArm: boolean, fenceArm: boolean): void { | |||
| if (isTurnOn && isReady && (fireArm || fenceArm)) { | |||
| //this.alertDuration = // setting time | |||
| this.playSound(); | |||
| }else{ | |||
| const alarmTime = this.getTimeDuration('alarmtime'); | |||
| const whistleTime = this.getTimeDuration('whistletime'); | |||
| if (fireArm && fireStatus && fenceArm && fenceStatus) { | |||
| this.alertDuration = Math.max(alarmTime, whistleTime); | |||
| } else if (fireArm && fireStatus) { | |||
| this.alertDuration = alarmTime; | |||
| } else if (fenceArm && fenceStatus) { | |||
| this.alertDuration = whistleTime; | |||
| } else { | |||
| this.stopAlarm(); | |||
| return; | |||
| } | |||
| this.playSound(); | |||
| } | |||
| stopAlarm(): void { | |||
| this.stopSound(); | |||
| if (this.audio) { | |||
| this.audio.pause(); | |||
| this.audio.currentTime = 0; | |||
| this.isPlay = false; | |||
| } | |||
| if (this.alertInterval){ | |||
| clearInterval(this.alertInterval); | |||
| } | |||
| } | |||
| getTimeDuration(key: string) { | |||
| return parseInt(localStorage.getItem(key) ?? '0') * 100; | |||
| } | |||
| } | |||
| @@ -14,8 +14,9 @@ export class ConfirmDialogService { | |||
| openDialog(data: any[]): void { | |||
| if (!this.isDialogOpen) { | |||
| this.isDialogOpen = true; | |||
| const dialogRef = this.dialog.open(ConfirmDialogComponent); | |||
| const dialogRef = this.dialog.open(ConfirmDialogComponent, { | |||
| data: data | |||
| }); | |||
| dialogRef | |||
| .afterClosed() | |||
| .pipe(take(1)) | |||