docs: finalize UML and ER diagrams for WalkGuide architecture
This commit is contained in:
parent
20e458e406
commit
2cbd6bda3a
Binary file not shown.
BIN
ooad-docs/diagrams/Er Diagram WalkGuide RILL FIX.drawio.pdf
Normal file
BIN
ooad-docs/diagrams/Er Diagram WalkGuide RILL FIX.drawio.pdf
Normal file
Binary file not shown.
@ -1,35 +1,542 @@
|
|||||||
@startuml
|
@startuml WalkGuide_Component_Diagram
|
||||||
|
title Component Diagram — WalkGuide System (v2.0)
|
||||||
|
|
||||||
skinparam componentStyle rectangle
|
skinparam componentStyle rectangle
|
||||||
|
skinparam backgroundColor #FAFAFA
|
||||||
component "Flutter Mobile App" {
|
skinparam component {
|
||||||
[Presentation Screens]
|
BackgroundColor #EEF2FF
|
||||||
[ApiClient + Interceptors]
|
BorderColor #6366F1
|
||||||
[TTS/STT/YOLO/Location Services]
|
FontColor #1E1B4B
|
||||||
[WebSocketService]
|
FontSize 11
|
||||||
[CallService]
|
}
|
||||||
|
skinparam package {
|
||||||
|
BackgroundColor #F0F4FF
|
||||||
|
BorderColor #818CF8
|
||||||
|
FontColor #1E1B4B
|
||||||
|
FontSize 12
|
||||||
|
FontStyle bold
|
||||||
|
}
|
||||||
|
skinparam interface {
|
||||||
|
BackgroundColor #DBEAFE
|
||||||
|
BorderColor #3B82F6
|
||||||
|
}
|
||||||
|
skinparam database {
|
||||||
|
BackgroundColor #FEF3C7
|
||||||
|
BorderColor #D97706
|
||||||
|
}
|
||||||
|
skinparam cloud {
|
||||||
|
BackgroundColor #F0FDF4
|
||||||
|
BorderColor #16A34A
|
||||||
|
}
|
||||||
|
skinparam arrow {
|
||||||
|
Color #4B5563
|
||||||
|
FontSize 9
|
||||||
|
FontColor #374151
|
||||||
|
}
|
||||||
|
skinparam note {
|
||||||
|
BackgroundColor #FFFBEB
|
||||||
|
BorderColor #F59E0B
|
||||||
|
FontSize 9
|
||||||
}
|
}
|
||||||
|
|
||||||
component "Spring Boot Backend" {
|
' ─────────────────────────────────────────────
|
||||||
[Controllers]
|
' MOBILE DEVICE — Flutter App
|
||||||
[Services]
|
' ─────────────────────────────────────────────
|
||||||
[Repositories]
|
package "Mobile Device (Flutter App)" as FLUTTER #E8EAF6 {
|
||||||
[Security/JWT]
|
|
||||||
[WebSocket Broker]
|
package "Presentation Layer" as PRESENTATION {
|
||||||
|
[ServerConnectScreen] as SCS
|
||||||
|
[SplashScreen] as SS
|
||||||
|
[LoginScreen] as LS
|
||||||
|
[RegisterScreen] as RS
|
||||||
|
|
||||||
|
package "User Screens" as US_SCREENS {
|
||||||
|
[WalkGuideScreen] as WGS
|
||||||
|
[SosScreen] as SOSS
|
||||||
|
[ActivityLogScreen] as ALS
|
||||||
|
[NotificationScreen] as NS
|
||||||
|
[NavigationModeScreen] as NMS
|
||||||
|
[UserSettingsScreen] as USS
|
||||||
|
[CallScreen] as CS
|
||||||
|
[ManualScreen] as MS
|
||||||
|
[UserPairingScreen] as UPS
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Guardian Screens" as GS_SCREENS {
|
||||||
|
[GuardianDashboardScreen] as GDS
|
||||||
|
[GuardianMapScreen] as GMS
|
||||||
|
[GuardianActivityLogScreen] as GALS
|
||||||
|
[GuardianSendNotifScreen] as GSNS
|
||||||
|
[GuardianAiConfigScreen] as GACS
|
||||||
|
[GuardianVoiceCmdScreen] as GVCS
|
||||||
|
[GuardianShortcutScreen] as GSHS
|
||||||
|
[GuardianGeofenceScreen] as GGFS
|
||||||
|
[GuardianPairingScreen] as GPS
|
||||||
|
[IncomingCallScreen] as ICS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Application Layer (BLoC / Cubit)" as BLOC_LAYER {
|
||||||
|
[AuthBloc] as AuthBloc
|
||||||
|
[PairingBloc] as PairingBloc
|
||||||
|
[WalkGuideBloc] as WGBloc
|
||||||
|
[SosBloc] as SosBloc
|
||||||
|
[LocationBloc] as LocBloc
|
||||||
|
[NotificationBloc] as NotifBloc
|
||||||
|
[ActivityLogBloc] as ActLogBloc
|
||||||
|
[VoiceCommandBloc] as VCBloc
|
||||||
|
[ServerConnectBloc] as SCBloc
|
||||||
|
[CallBloc] as CallBloc
|
||||||
|
[GuardianDashboardBloc] as GDBloc
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Domain Layer" as DOMAIN {
|
||||||
|
package "Use Cases" as USECASES {
|
||||||
|
[LoginUseCase] as LoginUC
|
||||||
|
[RegisterUseCase] as RegUC
|
||||||
|
[LogoutUseCase] as LogoutUC
|
||||||
|
[InviteUserUseCase] as InviteUC
|
||||||
|
[RespondPairingUseCase] as RespUC
|
||||||
|
[UnpairUseCase] as UnpairUC
|
||||||
|
[StartSessionUseCase] as StartUC
|
||||||
|
[StopSessionUseCase] as StopUC
|
||||||
|
[LogObstacleUseCase] as LogObsUC
|
||||||
|
[TriggerSosUseCase] as TrigSosUC
|
||||||
|
[UpdateLocationUseCase] as UpdLocUC
|
||||||
|
}
|
||||||
|
package "Repository Interfaces" as REPO_INTF {
|
||||||
|
interface "IAuthRepository" as IAuthRepo
|
||||||
|
interface "IPairingRepository" as IPairRepo
|
||||||
|
interface "IWalkGuideRepository" as IWGRepo
|
||||||
|
interface "IActivityLogRepository" as IActRepo
|
||||||
|
interface "ISosRepository" as ISosRepo
|
||||||
|
interface "ILocationRepository" as ILocRepo
|
||||||
|
interface "INotificationRepository" as INotifRepo
|
||||||
|
interface "IAiConfigRepository" as IAIRepo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Data Layer" as DATA {
|
||||||
|
package "Repository Implementations" as REPO_IMPL {
|
||||||
|
[AuthRepositoryImpl] as AuthRepoImpl
|
||||||
|
[PairingRepositoryImpl] as PairRepoImpl
|
||||||
|
[WalkGuideRepositoryImpl] as WGRepoImpl
|
||||||
|
[ActivityLogRepositoryImpl] as ActRepoImpl
|
||||||
|
[SosRepositoryImpl] as SosRepoImpl
|
||||||
|
[LocationRepositoryImpl] as LocRepoImpl
|
||||||
|
[NotificationRepositoryImpl] as NotifRepoImpl
|
||||||
|
[AiConfigRepositoryImpl] as AIRepoImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Remote Data Sources" as REMOTE_DS {
|
||||||
|
[AuthRemoteDataSource] as AuthDS
|
||||||
|
[WalkGuideRemoteDataSource] as WGDS
|
||||||
|
[ActivityLogRemoteDataSource] as ActDS
|
||||||
|
[SosRemoteDataSource] as SosDS
|
||||||
|
[LocationRemoteDataSource] as LocDS
|
||||||
|
[NotificationRemoteDataSource] as NotifDS
|
||||||
|
[GuardianRemoteDataSource] as GrdDS
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Local Data Sources (Drift/SQLite)" as LOCAL_DS {
|
||||||
|
[SecureStorageService] as SecStore
|
||||||
|
[LocalDatabase (Drift)] as DriftDB
|
||||||
|
[SharedPreferencesService] as SharedPref
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Core Services (Singletons)" as CORE_SVC {
|
||||||
|
[TtsService\n(flutter_tts)] as TTS
|
||||||
|
[SttService\n(speech_to_text)] as STT
|
||||||
|
[VoiceCommandHandler] as VCH
|
||||||
|
[HapticService\n(vibration)] as HAP
|
||||||
|
[HardwareShortcutListener] as HSL
|
||||||
|
[FcmService\n(firebase_messaging)] as FCMSvc
|
||||||
|
[WebSocketService\n(stomp_dart_client)] as WSSvc
|
||||||
|
[AgoraService\n(agora_rtc_engine)] as AgoraSvc
|
||||||
|
}
|
||||||
|
|
||||||
|
package "AI Engine (On-Device)" as AI_ENGINE {
|
||||||
|
[ModelLoader\n(tflite_flutter)] as ModelLoader
|
||||||
|
[YoloDetector\n(YOLOv8n)] as YOLO
|
||||||
|
[ObstacleAnalyzer] as ObsAnalyzer
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiClient\n(Dio + Interceptors)] as DioClient
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
database "PostgreSQL" as DB
|
' ─────────────────────────────────────────────
|
||||||
cloud "Firebase FCM" as FCM
|
' BACKEND SERVER — Spring Boot
|
||||||
cloud "Agora RTC" as Agora
|
' ─────────────────────────────────────────────
|
||||||
cloud "OpenStreetMap/OSRM" as Maps
|
package "Backend Server (Spring Boot @ 202.46.28.160:8080)" as BACKEND #E8F5E9 {
|
||||||
|
|
||||||
|
package "Security & Config" as SEC_CFG {
|
||||||
|
[SecurityConfig\n(CORS, CSRF, RBAC)] as SecConfig
|
||||||
|
[JwtAuthFilter\n(OncePerRequestFilter)] as JWTFilter
|
||||||
|
[JwtUtil\n(JJWT)] as JWTUtil
|
||||||
|
[CustomUserDetailsService] as CUDS
|
||||||
|
[WebSocketConfig\n(STOMP)] as WSConfig
|
||||||
|
[GlobalExceptionHandler\n(@ControllerAdvice)] as GEH
|
||||||
|
[SwaggerConfig\n(Springdoc OpenAPI)] as SwagConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Controller Layer" as CTRL {
|
||||||
|
[AuthController\n/api/v1/auth] as AuthCtrl
|
||||||
|
[PairingController\n/api/v1/shared/pairing] as PairCtrl
|
||||||
|
[GuardianController\n/api/v1/guardian] as GrdCtrl
|
||||||
|
[UserController\n/api/v1/user] as UserCtrl
|
||||||
|
[CallController\n/api/v1/shared/call] as CallCtrl
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Service Layer" as SVC {
|
||||||
|
[AuthService] as AuthSvc
|
||||||
|
[PairingService] as PairSvc
|
||||||
|
[ActivityLogService] as ActLogSvc
|
||||||
|
[LocationService\n(+ Haversine)] as LocSvc
|
||||||
|
[ObstacleLogService] as ObsLogSvc
|
||||||
|
[NotificationService] as NotifSvc
|
||||||
|
[SosService] as SosSvc
|
||||||
|
[AiConfigService] as AICfgSvc
|
||||||
|
[VoiceCommandService] as VCSvc
|
||||||
|
[HardwareShortcutService] as HWSvc
|
||||||
|
[GeofenceService\n(Haversine)] as GeoSvc
|
||||||
|
[UserSettingsService] as UserSetSvc
|
||||||
|
[FcmService\n(Firebase Admin)] as FcmBackSvc
|
||||||
|
[AgoraTokenService] as AgoraSvcBE
|
||||||
|
[GuardianDashboardService] as GDSvc
|
||||||
|
}
|
||||||
|
|
||||||
|
package "WebSocket Broadcaster" as WS_BROADCAST {
|
||||||
|
[LocationBroadcaster\n(SimpMessagingTemplate)] as LocBcast
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Repository Layer (Spring Data JPA)" as REPO {
|
||||||
|
[UserRepository] as UserRepo
|
||||||
|
[PairingRelationRepository] as PairRepo
|
||||||
|
[ActivityLogRepository] as ActRepo
|
||||||
|
[ObstacleLogRepository] as ObsRepo
|
||||||
|
[LocationHistoryRepository] as LocRepo
|
||||||
|
[GuardianNotificationRepository] as NotifRepo
|
||||||
|
[SosEventRepository] as SosRepo
|
||||||
|
[UserSettingsRepository] as UserSetRepo
|
||||||
|
[AiConfigRepository] as AIRepo
|
||||||
|
[VoiceCommandConfigRepository] as VCRepo
|
||||||
|
[HardwareShortcutRepository] as HWRepo
|
||||||
|
[GeofenceConfigRepository] as GeoRepo
|
||||||
|
[RefreshTokenRepository] as RTRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Domain / Entity" as ENTITY {
|
||||||
|
[User] as UEnt
|
||||||
|
[PairingRelation] as PRel
|
||||||
|
[ActivityLog] as ActEnt
|
||||||
|
[ObstacleLog] as ObsEnt
|
||||||
|
[LocationHistory] as LocEnt
|
||||||
|
[GuardianNotification] as NotifEnt
|
||||||
|
[SosEvent] as SosEnt
|
||||||
|
[UserSettings] as UserSetEnt
|
||||||
|
[AiConfig] as AICfgEnt
|
||||||
|
[VoiceCommandConfig] as VCEnt
|
||||||
|
[HardwareShortcut] as HWEnt
|
||||||
|
[GeofenceConfig] as GeoEnt
|
||||||
|
[RefreshToken] as RTEnt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' EXTERNAL SERVICES
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
package "External Services" as EXTERNAL #FFF8E1 {
|
||||||
|
cloud "Firebase (FCM)\nPush Notifications" as FCM
|
||||||
|
cloud "Agora RTC\nVoIP Audio Call" as AGORA
|
||||||
|
cloud "OpenStreetMap\n(flutter_map tiles)" as OSM
|
||||||
|
}
|
||||||
|
|
||||||
|
database "PostgreSQL\n202.46.28.160:2002\n(University Server)\nFlyway Migrations V1-V16" as PGDB
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' PRESENTATION → BLOC
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
SCS --> SCBloc
|
||||||
|
SS --> AuthBloc
|
||||||
|
LS --> AuthBloc
|
||||||
|
RS --> AuthBloc
|
||||||
|
WGS --> WGBloc
|
||||||
|
WGS --> VCBloc
|
||||||
|
SOSS --> SosBloc
|
||||||
|
ALS --> ActLogBloc
|
||||||
|
NS --> NotifBloc
|
||||||
|
NMS --> LocBloc
|
||||||
|
USS --> AuthBloc
|
||||||
|
CS --> CallBloc
|
||||||
|
UPS --> PairingBloc
|
||||||
|
GDS --> GDBloc
|
||||||
|
GMS --> LocBloc
|
||||||
|
GALS --> ActLogBloc
|
||||||
|
GSNS --> NotifBloc
|
||||||
|
GACS --> AIRepoImpl
|
||||||
|
GVCS --> VCBloc
|
||||||
|
GSHS --> VCBloc
|
||||||
|
GGFS --> GDBloc
|
||||||
|
GPS --> PairingBloc
|
||||||
|
ICS --> CallBloc
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' BLOC → USE CASES
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
AuthBloc --> LoginUC
|
||||||
|
AuthBloc --> RegUC
|
||||||
|
AuthBloc --> LogoutUC
|
||||||
|
PairingBloc --> InviteUC
|
||||||
|
PairingBloc --> RespUC
|
||||||
|
PairingBloc --> UnpairUC
|
||||||
|
WGBloc --> StartUC
|
||||||
|
WGBloc --> StopUC
|
||||||
|
WGBloc --> LogObsUC
|
||||||
|
SosBloc --> TrigSosUC
|
||||||
|
LocBloc --> UpdLocUC
|
||||||
|
CallBloc --> AgoraSvc
|
||||||
|
VCBloc --> VCH
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' USE CASES → REPOSITORY INTERFACES
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
LoginUC --> IAuthRepo
|
||||||
|
RegUC --> IAuthRepo
|
||||||
|
LogoutUC --> IAuthRepo
|
||||||
|
InviteUC --> IPairRepo
|
||||||
|
RespUC --> IPairRepo
|
||||||
|
UnpairUC --> IPairRepo
|
||||||
|
StartUC --> IWGRepo
|
||||||
|
StopUC --> IWGRepo
|
||||||
|
LogObsUC --> IWGRepo
|
||||||
|
TrigSosUC --> ISosRepo
|
||||||
|
UpdLocUC --> ILocRepo
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' INTERFACES → IMPLEMENTATIONS
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
IAuthRepo <|.. AuthRepoImpl
|
||||||
|
IPairRepo <|.. PairRepoImpl
|
||||||
|
IWGRepo <|.. WGRepoImpl
|
||||||
|
IActRepo <|.. ActRepoImpl
|
||||||
|
ISosRepo <|.. SosRepoImpl
|
||||||
|
ILocRepo <|.. LocRepoImpl
|
||||||
|
INotifRepo <|.. NotifRepoImpl
|
||||||
|
IAIRepo <|.. AIRepoImpl
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' REPO IMPL → DATA SOURCES
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
AuthRepoImpl --> AuthDS
|
||||||
|
AuthRepoImpl --> SecStore
|
||||||
|
PairRepoImpl --> GrdDS
|
||||||
|
WGRepoImpl --> WGDS
|
||||||
|
WGRepoImpl --> DriftDB
|
||||||
|
ActRepoImpl --> ActDS
|
||||||
|
SosRepoImpl --> SosDS
|
||||||
|
LocRepoImpl --> LocDS
|
||||||
|
NotifRepoImpl --> NotifDS
|
||||||
|
NotifRepoImpl --> DriftDB
|
||||||
|
AIRepoImpl --> GrdDS
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' REMOTE DATA SOURCES → DioClient
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
AuthDS --> DioClient
|
||||||
|
WGDS --> DioClient
|
||||||
|
ActDS --> DioClient
|
||||||
|
SosDS --> DioClient
|
||||||
|
LocDS --> DioClient
|
||||||
|
NotifDS --> DioClient
|
||||||
|
GrdDS --> DioClient
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' CORE SERVICES INTERCONNECT
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
WGBloc --> YOLO
|
||||||
|
WGBloc --> TTS
|
||||||
|
WGBloc --> HAP
|
||||||
|
YOLO --> ModelLoader
|
||||||
|
YOLO --> ObsAnalyzer
|
||||||
|
ObsAnalyzer --> TTS
|
||||||
|
ObsAnalyzer --> HAP
|
||||||
|
VCH --> STT
|
||||||
|
VCH --> DriftDB
|
||||||
|
HSL --> DriftDB
|
||||||
|
FCMSvc --> DriftDB
|
||||||
|
|
||||||
|
SharedPref --> DioClient : baseUrl
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' DioClient → Backend REST API
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
DioClient -down-> AuthCtrl : "POST /api/v1/auth/register\nPOST /api/v1/auth/login\nPOST /api/v1/auth/refresh\nPOST /api/v1/auth/logout\nPUT /api/v1/auth/fcm-token\nGET /api/v1/auth/ping"
|
||||||
|
DioClient -down-> PairCtrl : "POST /shared/pairing/invite\nPOST /shared/pairing/respond\nDELETE /shared/pairing/unpair\nGET /shared/pairing/status"
|
||||||
|
DioClient -down-> UserCtrl : "GET /user/profile\nPOST /user/location\nPOST /user/obstacle\nPOST /user/sos\nGET /user/notifications\nPOST /user/walkguide/start\nPOST /user/walkguide/stop"
|
||||||
|
DioClient -down-> GrdCtrl : "GET /guardian/dashboard\nGET /guardian/user-location\nPOST /guardian/notifications/send\nPUT /guardian/ai-config\nPUT /guardian/geofence"
|
||||||
|
DioClient -down-> CallCtrl : "POST /shared/call/token\nPOST /shared/call/notify"
|
||||||
|
|
||||||
|
' WebSocket STOMP
|
||||||
|
WSSvc -down-> WSConfig : "ws://host:8080/ws\n(STOMP)\n/topic/location/{userId}\n/queue/sos/{guardianId}\n/queue/notif/{userId}"
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' BACKEND SECURITY FLOW
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
JWTFilter --> JWTUtil : validate token
|
||||||
|
JWTFilter --> CUDS : load user
|
||||||
|
JWTFilter -up-> SecConfig : filter chain
|
||||||
|
SecConfig -right-> JWTFilter
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' CONTROLLERS → SERVICES
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
AuthCtrl --> AuthSvc
|
||||||
|
PairCtrl --> PairSvc
|
||||||
|
GrdCtrl --> GDSvc
|
||||||
|
GrdCtrl --> LocSvc
|
||||||
|
GrdCtrl --> ActLogSvc
|
||||||
|
GrdCtrl --> ObsLogSvc
|
||||||
|
GrdCtrl --> NotifSvc
|
||||||
|
GrdCtrl --> SosSvc
|
||||||
|
GrdCtrl --> AICfgSvc
|
||||||
|
GrdCtrl --> VCSvc
|
||||||
|
GrdCtrl --> HWSvc
|
||||||
|
GrdCtrl --> GeoSvc
|
||||||
|
UserCtrl --> AuthSvc
|
||||||
|
UserCtrl --> LocSvc
|
||||||
|
UserCtrl --> ObsLogSvc
|
||||||
|
UserCtrl --> SosSvc
|
||||||
|
UserCtrl --> ActLogSvc
|
||||||
|
UserCtrl --> NotifSvc
|
||||||
|
UserCtrl --> UserSetSvc
|
||||||
|
UserCtrl --> VCSvc
|
||||||
|
UserCtrl --> HWSvc
|
||||||
|
UserCtrl --> AICfgSvc
|
||||||
|
CallCtrl --> AgoraSvcBE
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' SERVICES CROSS-DEPENDENCIES
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
AuthSvc --> PairSvc : seed defaults on register
|
||||||
|
PairSvc --> VCSvc : seedDefaults()
|
||||||
|
PairSvc --> HWSvc : seedDefaults()
|
||||||
|
PairSvc --> AICfgSvc : create config on pair
|
||||||
|
PairSvc --> FcmBackSvc : send FCM invite/response
|
||||||
|
NotifSvc --> FcmBackSvc : send FCM notification
|
||||||
|
SosSvc --> FcmBackSvc : send FCM SOS high-priority
|
||||||
|
AICfgSvc --> FcmBackSvc : notify settings updated
|
||||||
|
LocSvc --> GeoSvc : checkGeofence()
|
||||||
|
LocSvc --> LocBcast : broadcastLocation()
|
||||||
|
LocSvc --> ActLogSvc : log LOCATION_UPDATE
|
||||||
|
NotifSvc --> LocBcast : broadcastNotification()
|
||||||
|
SosSvc --> LocBcast : broadcastSos()
|
||||||
|
SosSvc --> ActLogSvc : log SOS_TRIGGERED
|
||||||
|
ActLogSvc --> LocBcast : broadcast to Guardian
|
||||||
|
GDSvc --> LocSvc
|
||||||
|
GDSvc --> ActLogSvc
|
||||||
|
GDSvc --> NotifSvc
|
||||||
|
GDSvc --> SosSvc
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' SERVICES → REPOSITORIES
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
AuthSvc --> UserRepo
|
||||||
|
AuthSvc --> RTRepo
|
||||||
|
AuthSvc --> ActLogSvc
|
||||||
|
PairSvc --> PairRepo
|
||||||
|
PairSvc --> UserRepo
|
||||||
|
ActLogSvc --> ActRepo
|
||||||
|
LocSvc --> LocRepo
|
||||||
|
LocSvc --> PairRepo
|
||||||
|
ObsLogSvc --> ObsRepo
|
||||||
|
ObsLogSvc --> ActLogSvc
|
||||||
|
NotifSvc --> NotifRepo
|
||||||
|
NotifSvc --> PairRepo
|
||||||
|
SosSvc --> SosRepo
|
||||||
|
SosSvc --> PairRepo
|
||||||
|
AICfgSvc --> AIRepo
|
||||||
|
VCSvc --> VCRepo
|
||||||
|
HWSvc --> HWRepo
|
||||||
|
GeoSvc --> GeoRepo
|
||||||
|
UserSetSvc --> UserSetRepo
|
||||||
|
FcmBackSvc --> UserRepo
|
||||||
|
AgoraSvcBE --> UserRepo
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' REPOSITORIES → ENTITIES (JPA/Hibernate)
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
UserRepo --> UEnt
|
||||||
|
PairRepo --> PRel
|
||||||
|
ActRepo --> ActEnt
|
||||||
|
ObsRepo --> ObsEnt
|
||||||
|
LocRepo --> LocEnt
|
||||||
|
NotifRepo --> NotifEnt
|
||||||
|
SosRepo --> SosEnt
|
||||||
|
UserSetRepo --> UserSetEnt
|
||||||
|
AIRepo --> AICfgEnt
|
||||||
|
VCRepo --> VCEnt
|
||||||
|
HWRepo --> HWEnt
|
||||||
|
GeoRepo --> GeoEnt
|
||||||
|
RTRepo --> RTEnt
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' ENTITIES → DATABASE
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
UEnt --> PGDB : JPA/Hibernate
|
||||||
|
PRel --> PGDB
|
||||||
|
ActEnt --> PGDB
|
||||||
|
ObsEnt --> PGDB
|
||||||
|
LocEnt --> PGDB
|
||||||
|
NotifEnt --> PGDB
|
||||||
|
SosEnt --> PGDB
|
||||||
|
UserSetEnt --> PGDB
|
||||||
|
AICfgEnt --> PGDB
|
||||||
|
VCEnt --> PGDB
|
||||||
|
HWEnt --> PGDB
|
||||||
|
GeoEnt --> PGDB
|
||||||
|
RTEnt --> PGDB
|
||||||
|
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
' EXTERNAL SERVICES
|
||||||
|
' ─────────────────────────────────────────────
|
||||||
|
FcmBackSvc -right-> FCM : "Firebase Admin SDK\nFirebaseMessaging.send()"
|
||||||
|
AgoraSvcBE -right-> AGORA : "Generate RTC Token\n(server-side Agora SDK)"
|
||||||
|
AgoraSvc --> AGORA : "agora_rtc_engine\nJoin Channel (audio-only)"
|
||||||
|
FCMSvc --> FCM : "firebase_messaging\nReceive push"
|
||||||
|
GMS --> OSM : "flutter_map\nOpenStreetMap tiles"
|
||||||
|
|
||||||
|
note top of PGDB
|
||||||
|
Tables (Flyway V1–V16):
|
||||||
|
users, pairing_relations,
|
||||||
|
activity_logs, obstacle_logs,
|
||||||
|
location_history, guardian_notifications,
|
||||||
|
sos_events, user_settings, ai_configs,
|
||||||
|
voice_command_configs, hardware_shortcuts,
|
||||||
|
geofence_configs, refresh_tokens
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of AI_ENGINE
|
||||||
|
On-Device AI (No server call):
|
||||||
|
YOLOv8n .tflite (640×640)
|
||||||
|
→ detect obstacles
|
||||||
|
→ direction: LEFT/CENTER/RIGHT
|
||||||
|
→ TTS + Haptic alert
|
||||||
|
NNAPI delegate (Android AI accel.)
|
||||||
|
end note
|
||||||
|
|
||||||
|
note left of SharedPref
|
||||||
|
Dynamic Server URL:
|
||||||
|
User inputs "http://202.46.28.160:8080"
|
||||||
|
Saved to SharedPreferences
|
||||||
|
No hardcoded BASE_URL in APK
|
||||||
|
end note
|
||||||
|
|
||||||
|
note bottom of WSSvc
|
||||||
|
WebSocket STOMP:
|
||||||
|
/topic/location/{userId} → Guardian map real-time
|
||||||
|
/queue/sos/{guardianId} → SOS alert instant
|
||||||
|
/queue/notif/{userId} → Notification push
|
||||||
|
end note
|
||||||
|
|
||||||
[Presentation Screens] --> [ApiClient + Interceptors]
|
|
||||||
[ApiClient + Interceptors] --> [Controllers] : REST /api/v1
|
|
||||||
[WebSocketService] --> [WebSocket Broker] : STOMP /ws
|
|
||||||
[CallService] --> [Controllers] : /shared/call
|
|
||||||
[Controllers] --> [Services]
|
|
||||||
[Services] --> [Repositories]
|
|
||||||
[Repositories] --> DB
|
|
||||||
[Services] --> FCM
|
|
||||||
[CallService] --> Agora
|
|
||||||
[Presentation Screens] --> Maps
|
|
||||||
@enduml
|
@enduml
|
||||||
|
|||||||
381
ooad-docs/diagrams/sequence-diagram.puml
Normal file
381
ooad-docs/diagrams/sequence-diagram.puml
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
@startuml
|
||||||
|
skinparam sequenceMessageAlign center
|
||||||
|
skinparam responseMessageBelowArrow true
|
||||||
|
skinparam maxMessageSize 150
|
||||||
|
skinparam sequence {
|
||||||
|
ParticipantBackgroundColor LightBlue
|
||||||
|
ActorBackgroundColor LightGray
|
||||||
|
LifeLineBorderColor gray
|
||||||
|
GroupBorderColor DarkGray
|
||||||
|
}
|
||||||
|
|
||||||
|
title WalkGuide System — Complete Sequence Diagram
|
||||||
|
|
||||||
|
' ==========================================
|
||||||
|
' PARTICIPANTS
|
||||||
|
' ==========================================
|
||||||
|
actor "Tunanetra\n(ROLE_USER)" as User
|
||||||
|
actor "Pendamping\n(ROLE_GUARDIAN)" as Guardian
|
||||||
|
participant "Flutter App\n(User Side)" as AppUser
|
||||||
|
participant "Flutter App\n(Guardian Side)" as AppGuardian
|
||||||
|
participant "Spring Boot\nBackend" as Backend
|
||||||
|
participant "JWT Auth\nFilter" as JWT
|
||||||
|
participant "WebSocket\n(STOMP)" as WS
|
||||||
|
participant "Firebase FCM" as FCM
|
||||||
|
participant "Agora RTC" as Agora
|
||||||
|
participant "YOLOv8n\n(On-Device AI)" as YOLO
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' 1. SERVER CONNECT
|
||||||
|
' ============================================================
|
||||||
|
== 1. Server Connect (Pertama Kali Install) ==
|
||||||
|
|
||||||
|
User -> AppUser : Buka aplikasi
|
||||||
|
AppUser -> AppUser : Cek SharedPreferences\n(serverUrl ada?)
|
||||||
|
alt serverUrl TIDAK ada
|
||||||
|
AppUser -> User : Tampilkan ServerConnectScreen
|
||||||
|
User -> AppUser : Input URL server\n(http://202.46.28.160:8080)
|
||||||
|
AppUser -> Backend : GET /api/v1/health (ping)
|
||||||
|
alt Server OK
|
||||||
|
Backend --> AppUser : 200 OK
|
||||||
|
AppUser -> AppUser : Simpan serverUrl\nke SharedPreferences
|
||||||
|
AppUser -> User : Redirect ke LoginScreen
|
||||||
|
else Server Gagal
|
||||||
|
Backend --> AppUser : Timeout / Error
|
||||||
|
AppUser -> User : Tampilkan pesan error\n"Server tidak dapat dijangkau"
|
||||||
|
end
|
||||||
|
else serverUrl sudah ada
|
||||||
|
AppUser -> User : Langsung ke SplashScreen
|
||||||
|
end
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' 2. AUTH — REGISTER
|
||||||
|
' ============================================================
|
||||||
|
== 2. Register Akun ==
|
||||||
|
|
||||||
|
User -> AppUser : Isi form Register\n(email, password, role)
|
||||||
|
AppUser -> Backend : POST /api/v1/auth/register\n{email, password, displayName, role}
|
||||||
|
Backend -> Backend : Validasi @NotBlank, @Email
|
||||||
|
Backend -> Backend : Cek email unik
|
||||||
|
Backend -> Backend : BCrypt encode password
|
||||||
|
alt role = ROLE_USER
|
||||||
|
Backend -> Backend : Generate uniqueUserId\n(12 char alphanumeric)
|
||||||
|
end
|
||||||
|
Backend -> Backend : Simpan user ke DB
|
||||||
|
Backend -> Backend : Buat default UserSettings
|
||||||
|
Backend -> Backend : Generate JWT access token (1 jam)\n+ refresh token (30 hari)
|
||||||
|
Backend -> Backend : Simpan refresh token ke DB
|
||||||
|
Backend --> AppUser : 201 Created\n{accessToken, refreshToken, role, userId, uniqueUserId}
|
||||||
|
AppUser -> AppUser : Simpan token ke\nFlutterSecureStorage
|
||||||
|
AppUser -> Backend : PUT /api/v1/shared/fcm-token\n{fcmToken}
|
||||||
|
Backend --> AppUser : 200 OK
|
||||||
|
AppUser -> User : Redirect ke Home Screen\n(berdasarkan role)
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' 3. AUTH — LOGIN
|
||||||
|
' ============================================================
|
||||||
|
== 3. Login ==
|
||||||
|
|
||||||
|
User -> AppUser : Input email & password
|
||||||
|
AppUser -> Backend : POST /api/v1/auth/login\n{email, password}
|
||||||
|
Backend -> Backend : findByEmail → validasi BCrypt
|
||||||
|
Backend -> Backend : Generate JWT access token\n+ refresh token
|
||||||
|
Backend -> Backend : Simpan refresh token ke DB
|
||||||
|
Backend -> Backend : Log ActivityLog: LOGIN
|
||||||
|
Backend --> AppUser : 200 OK\n{accessToken, refreshToken, role, userId}
|
||||||
|
AppUser -> AppUser : Simpan token ke\nFlutterSecureStorage
|
||||||
|
AppUser -> Backend : PUT /api/v1/shared/fcm-token\n{fcmToken}
|
||||||
|
Backend --> AppUser : 200 OK
|
||||||
|
AppUser -> WS : STOMP connect\n(Authorization: Bearer token)
|
||||||
|
WS --> AppUser : Connected
|
||||||
|
AppUser -> User : Redirect ke Home\n(ROLE_USER → /user/home)
|
||||||
|
|
||||||
|
Guardian -> AppGuardian : Login
|
||||||
|
AppGuardian -> Backend : POST /api/v1/auth/login
|
||||||
|
Backend --> AppGuardian : 200 OK {accessToken, role=ROLE_GUARDIAN}
|
||||||
|
AppGuardian -> WS : STOMP connect
|
||||||
|
WS --> AppGuardian : Connected
|
||||||
|
AppGuardian -> Guardian : Redirect ke /guardian/home
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' 4. AUTH — JWT VALIDATE & REFRESH
|
||||||
|
' ============================================================
|
||||||
|
== 4. JWT Validate & Auto-Refresh ==
|
||||||
|
|
||||||
|
AppUser -> Backend : Request API (any endpoint)\n[Authorization: Bearer accessToken]
|
||||||
|
Backend -> JWT : Validasi token
|
||||||
|
alt Token valid
|
||||||
|
JWT --> Backend : OK, userId + role
|
||||||
|
Backend -> Backend : RBAC check role
|
||||||
|
Backend --> AppUser : 200 Response
|
||||||
|
else Token expired (401)
|
||||||
|
JWT --> Backend : 401 Unauthorized
|
||||||
|
Backend --> AppUser : 401 Unauthorized
|
||||||
|
AppUser -> Backend : POST /api/v1/auth/refresh\n{refreshToken}
|
||||||
|
Backend -> Backend : Cek refresh token di DB\n+ cek expired
|
||||||
|
alt Refresh token valid
|
||||||
|
Backend -> Backend : Generate access token baru
|
||||||
|
Backend --> AppUser : 200 OK {accessToken baru}
|
||||||
|
AppUser -> AppUser : Update token di SecureStorage
|
||||||
|
AppUser -> Backend : Retry request original
|
||||||
|
Backend --> AppUser : 200 Response
|
||||||
|
else Refresh token expired
|
||||||
|
Backend --> AppUser : 401 Unauthorized
|
||||||
|
AppUser -> AppUser : Clear semua token
|
||||||
|
AppUser -> User : Redirect ke /login
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' 5. PAIRING
|
||||||
|
' ============================================================
|
||||||
|
== 5. Pairing — Guardian Undang User ==
|
||||||
|
|
||||||
|
Guardian -> AppGuardian : Input uniqueUserId\n(12 char) milik User
|
||||||
|
AppGuardian -> Backend : POST /api/v1/guardian/pairing/invite\n{uniqueUserId}
|
||||||
|
Backend -> Backend : Cek User exist by uniqueUserId
|
||||||
|
Backend -> Backend : Cek belum ada pairing aktif
|
||||||
|
Backend -> Backend : Buat PairingRelation\n(status=PENDING)
|
||||||
|
Backend -> FCM : Kirim push notif ke User\n(type=PAIRING_INVITE)
|
||||||
|
FCM --> AppUser : Push notification diterima
|
||||||
|
AppUser -> AppUser : FCM Handler:\nrefresh PairingBloc
|
||||||
|
AppUser -> AppUser : TTS: "Anda mendapat\nundangan pairing"
|
||||||
|
Backend --> AppGuardian : 200 OK {pairingId, status=PENDING}
|
||||||
|
|
||||||
|
User -> AppUser : Buka notif / PairingScreen
|
||||||
|
AppUser -> Backend : GET /api/v1/user/pairing/pending
|
||||||
|
Backend --> AppUser : 200 OK {pairingId, guardianName}
|
||||||
|
User -> AppUser : Tap "Terima" atau "Tolak"
|
||||||
|
AppUser -> Backend : POST /api/v1/user/pairing/respond\n{pairingId, accept: true/false}
|
||||||
|
Backend -> Backend : Update PairingRelation\n(status=ACTIVE / REJECTED)
|
||||||
|
alt Accept
|
||||||
|
Backend -> Backend : Seed default VoiceCommandConfigs\n& HardwareShortcuts untuk pasangan ini
|
||||||
|
Backend -> FCM : Kirim notif ke Guardian\n(type=PAIRING_RESPONSE, accepted=true)
|
||||||
|
FCM --> AppGuardian : Push notification
|
||||||
|
AppGuardian -> AppGuardian : TTS Guardian:\n"User menerima pairing Anda"
|
||||||
|
Backend --> AppUser : 200 OK {status=ACTIVE}
|
||||||
|
AppUser -> User : TTS: "Pairing berhasil"
|
||||||
|
else Reject
|
||||||
|
Backend -> FCM : Kirim notif ke Guardian\n(type=PAIRING_RESPONSE, accepted=false)
|
||||||
|
Backend --> AppUser : 200 OK {status=REJECTED}
|
||||||
|
end
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' 6. WALK GUIDE MODE
|
||||||
|
' ============================================================
|
||||||
|
== 6. Aktifkan Walk Guide Mode ==
|
||||||
|
|
||||||
|
User -> AppUser : Tap tombol / ucap\n"Start Walkguide"
|
||||||
|
AppUser -> AppUser : VoiceCommandHandler:\nmatch trigger phrase
|
||||||
|
AppUser -> AppUser : WalkGuideBloc:\nadd(StartWalkGuide())
|
||||||
|
AppUser -> AppUser : Request permission:\nkamera, lokasi, mikrofon
|
||||||
|
AppUser -> YOLO : loadModel()\n(yolov8n.tflite dari assets)
|
||||||
|
YOLO --> AppUser : Model loaded
|
||||||
|
AppUser -> AppUser : Aktifkan kamera stream
|
||||||
|
AppUser -> AppUser : Aktifkan GPS stream\n(interval 5 detik)
|
||||||
|
AppUser -> User : TTS: "Walk Guide aktif.\nDeteksi obstacle dimulai."
|
||||||
|
|
||||||
|
loop Setiap frame kamera (max 5 FPS)
|
||||||
|
AppUser -> YOLO : detect(CameraImage frame)
|
||||||
|
YOLO -> YOLO : YUV420 → RGB → resize 640×640
|
||||||
|
YOLO -> YOLO : Normalize → run inference
|
||||||
|
YOLO -> YOLO : Post-process: NMS,\nfilter confidence threshold
|
||||||
|
YOLO --> AppUser : List<DetectionResult>\n{label, confidence, boundingBox}
|
||||||
|
alt Ada obstacle terdeteksi
|
||||||
|
AppUser -> AppUser : ObstacleAnalyzer:\nanalyzeDirection() + estimateDistance()
|
||||||
|
AppUser -> AppUser : buildTtsMessage():\n"Caution! {label} {direction}. {distance}."
|
||||||
|
AppUser -> AppUser : ttsService.speakImmediate(message)
|
||||||
|
AppUser -> User : 🔊 Audio TTS obstacle alert
|
||||||
|
AppUser -> AppUser : hapticService.obstacle\n{VeryClose/Close/Medium}()
|
||||||
|
AppUser -> User : 📳 Vibrasi sesuai jarak
|
||||||
|
AppUser -> Backend : POST /api/v1/user/obstacle-log\n{label, confidence, direction, dist, lat, lng}
|
||||||
|
Backend --> AppUser : 200 OK
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' 7. KIRIM LOKASI GPS REAL-TIME
|
||||||
|
' ============================================================
|
||||||
|
== 7. Kirim Lokasi GPS Real-time (WebSocket) ==
|
||||||
|
|
||||||
|
loop Setiap 5 detik (WalkGuide aktif) / 30 detik (idle)
|
||||||
|
AppUser -> AppUser : Geolocator:\ngetCurrentPosition()
|
||||||
|
AppUser -> WS : STOMP send\n/app/location/{userId}\n{lat, lng, accuracy, speed, heading}
|
||||||
|
WS -> Backend : Proses LocationUpdate
|
||||||
|
Backend -> Backend : Simpan ke location_history
|
||||||
|
WS -> AppGuardian : Broadcast ke\n/topic/location/{userId}
|
||||||
|
AppGuardian -> AppGuardian : Update marker\ndi flutter_map (real-time)
|
||||||
|
AppGuardian -> Guardian : 🗺️ Peta terupdate
|
||||||
|
|
||||||
|
alt User keluar radius Geofence
|
||||||
|
Backend -> Backend : Haversine: cek jarak\nke center geofence
|
||||||
|
Backend -> FCM : Push notif ke Guardian\n(type=GEOFENCE_EXIT)
|
||||||
|
FCM --> AppGuardian : Notifikasi masuk
|
||||||
|
AppGuardian -> Guardian : "User telah keluar\ndari area aman!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' 8. SOS DARURAT
|
||||||
|
' ============================================================
|
||||||
|
== 8. Kirim SOS Darurat ==
|
||||||
|
|
||||||
|
User -> AppUser : Ucap "Send SOS" /\ntap tombol SOS
|
||||||
|
AppUser -> AppUser : SosBloc: add(TriggerSos())
|
||||||
|
AppUser -> AppUser : hapticService.sosTriggered()\n[0,1000,200,1000,200,1000]
|
||||||
|
AppUser -> AppUser : ttsService.speak:\n"SOS dikirim ke Guardian"
|
||||||
|
AppUser -> Backend : POST /api/v1/user/sos\n{triggerType, lat, lng}
|
||||||
|
Backend -> Backend : Simpan SosEvent\n(status=TRIGGERED)
|
||||||
|
Backend -> Backend : Log ActivityLog: SOS_TRIGGERED
|
||||||
|
Backend -> FCM : Push notif ke Guardian\n(type=SOS_ALERT, lat, lng)
|
||||||
|
FCM --> AppGuardian : Push notification SOS
|
||||||
|
AppGuardian -> AppGuardian : FCM Handler:\nnavigate ke SOS/IncomingCallScreen
|
||||||
|
AppGuardian -> Guardian : 🚨 Alert SOS dengan lokasi User
|
||||||
|
Backend --> AppUser : 200 OK {sosEventId, status=TRIGGERED}
|
||||||
|
|
||||||
|
Guardian -> AppGuardian : Tap "Acknowledge"
|
||||||
|
AppGuardian -> Backend : PUT /api/v1/guardian/sos/{id}/acknowledge
|
||||||
|
Backend -> Backend : Update status=ACKNOWLEDGED\nsimpan acknowledged_at
|
||||||
|
Backend --> AppGuardian : 200 OK
|
||||||
|
AppGuardian -> Guardian : Tampilkan status\n"SOS Diakui"
|
||||||
|
|
||||||
|
Guardian -> AppGuardian : Tap "Resolved"
|
||||||
|
AppGuardian -> Backend : PUT /api/v1/guardian/sos/{id}/resolve
|
||||||
|
Backend -> Backend : Update status=RESOLVED
|
||||||
|
Backend --> AppGuardian : 200 OK
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' 9. VoIP CALL (AGORA)
|
||||||
|
' ============================================================
|
||||||
|
== 9. Panggil Guardian (VoIP Agora) ==
|
||||||
|
|
||||||
|
User -> AppUser : Ucap "Call Guardian" /\ntap tombol Call
|
||||||
|
AppUser -> Backend : POST /api/v1/shared/agora/token\n{channelName, uid}
|
||||||
|
Backend -> Agora : Generate Agora RTC token\n(server-side REST API)
|
||||||
|
Agora --> Backend : {token, channelName, uid, appId}
|
||||||
|
Backend --> AppUser : 200 OK {AgoraTokenResponse}
|
||||||
|
AppUser -> FCM : (via Backend) Push notif ke Guardian\n(type=INCOMING_CALL, channelName)
|
||||||
|
FCM --> AppGuardian : Push notification panggilan masuk
|
||||||
|
AppGuardian -> AppGuardian : Navigate ke IncomingCallScreen
|
||||||
|
AppGuardian -> Guardian : 📞 UI Panggilan Masuk\n(haptic + ringtone)
|
||||||
|
AppUser -> Agora : joinChannel(token, channelName, uid)\n(audio-only, speakerphone ON)
|
||||||
|
|
||||||
|
Guardian -> AppGuardian : Tap "Angkat"
|
||||||
|
AppGuardian -> Backend : POST /api/v1/shared/agora/token\n{channelName}
|
||||||
|
Backend --> AppGuardian : {AgoraTokenResponse}
|
||||||
|
AppGuardian -> Agora : joinChannel(token, channelName)
|
||||||
|
Agora --> AppUser : onUserJoined callback
|
||||||
|
Agora --> AppGuardian : onUserJoined callback
|
||||||
|
AppUser <-> AppGuardian : 🎙️ Audio call berlangsung\n(via Agora RTC)
|
||||||
|
|
||||||
|
alt Guardian tutup telepon
|
||||||
|
Guardian -> AppGuardian : Tap "Tutup"
|
||||||
|
AppGuardian -> Agora : leaveChannel()
|
||||||
|
Agora --> AppUser : onUserOffline callback
|
||||||
|
AppUser -> Agora : leaveChannel()
|
||||||
|
AppUser -> User : TTS: "Panggilan berakhir"
|
||||||
|
end
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' 10. NOTIFIKASI GUARDIAN → USER
|
||||||
|
' ============================================================
|
||||||
|
== 10. Kirim Notifikasi (Teks / Voice Note) ==
|
||||||
|
|
||||||
|
Guardian -> AppGuardian : Buka SendNotifScreen\nTulis pesan / rekam voice note
|
||||||
|
alt Notifikasi Teks
|
||||||
|
AppGuardian -> Backend : POST /api/v1/guardian/notification\n{notifType=TEXT, content}
|
||||||
|
else Voice Note
|
||||||
|
AppGuardian -> AppGuardian : record package:\nrekam audio
|
||||||
|
AppGuardian -> Backend : POST /api/v1/guardian/notification\n{notifType=VOICE_NOTE,\nvoiceNoteUrl, voiceNoteDuration}
|
||||||
|
end
|
||||||
|
Backend -> Backend : Simpan ke guardian_notifications
|
||||||
|
Backend -> FCM : Kirim push notif ke User\n(type=NOTIFICATION)
|
||||||
|
FCM --> AppUser : Push notification diterima
|
||||||
|
AppUser -> AppUser : FCM Handler: type=NOTIFICATION\n→ refresh NotificationBloc
|
||||||
|
AppUser -> AppUser : flutter_local_notifications:\ntampilkan notif lokal
|
||||||
|
AppUser -> AppUser : TTS: "Ada pesan baru\ndari Guardian"
|
||||||
|
AppUser -> User : 🔊 TTS pemberitahuan
|
||||||
|
Backend --> AppGuardian : 200 OK
|
||||||
|
|
||||||
|
User -> AppUser : Buka NotificationScreen\natau ucap "Read All My Notifications"
|
||||||
|
AppUser -> Backend : GET /api/v1/user/notifications
|
||||||
|
Backend --> AppUser : 200 OK List<NotificationResponse>
|
||||||
|
AppUser -> User : Tampilkan daftar notifikasi
|
||||||
|
alt Voice Note
|
||||||
|
User -> AppUser : Tap notifikasi voice note
|
||||||
|
AppUser -> AppUser : just_audio: play voiceNoteUrl
|
||||||
|
AppUser -> User : 🔊 Putar voice note Guardian
|
||||||
|
end
|
||||||
|
AppUser -> Backend : PUT /api/v1/user/notifications/read-all
|
||||||
|
Backend -> Backend : Update is_read=true semua notif
|
||||||
|
Backend --> AppUser : 200 OK
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' 11. GUARDIAN DASHBOARD
|
||||||
|
' ============================================================
|
||||||
|
== 11. Guardian Dashboard — Monitor & Konfigurasi ==
|
||||||
|
|
||||||
|
Guardian -> AppGuardian : Buka GuardianDashboardScreen
|
||||||
|
AppGuardian -> Backend : GET /api/v1/guardian/paired-user
|
||||||
|
Backend --> AppGuardian : 200 OK {UserProfileResponse}
|
||||||
|
AppGuardian -> Backend : GET /api/v1/guardian/location/latest
|
||||||
|
Backend --> AppGuardian : 200 OK {lat, lng, speed, heading}
|
||||||
|
AppGuardian -> AppGuardian : flutter_map: tampilkan\nmarker lokasi User
|
||||||
|
AppGuardian -> WS : Subscribe /topic/location/{userId}
|
||||||
|
AppGuardian -> Guardian : 🗺️ Dashboard dengan peta real-time
|
||||||
|
|
||||||
|
Guardian -> AppGuardian : Buka GuardianAiConfigScreen
|
||||||
|
AppGuardian -> Backend : GET /api/v1/guardian/ai-config
|
||||||
|
Backend --> AppGuardian : 200 OK {AiConfigResponse}
|
||||||
|
Guardian -> AppGuardian : Ubah threshold confidence,\ndistance, FPS, enabled labels
|
||||||
|
AppGuardian -> Backend : PUT /api/v1/guardian/ai-config\n{AiConfigUpdateRequest}
|
||||||
|
Backend -> Backend : Update ai_configs di DB
|
||||||
|
Backend -> FCM : Push notif ke User\n(type=SETTINGS_UPDATED)
|
||||||
|
FCM --> AppUser : Settings update diterima
|
||||||
|
AppUser -> Backend : GET /api/v1/user/ai-config
|
||||||
|
Backend --> AppUser : 200 OK {AiConfigResponse terbaru}
|
||||||
|
AppUser -> YOLO : Update confidenceThreshold\n& maxInferenceFps
|
||||||
|
Backend --> AppGuardian : 200 OK
|
||||||
|
|
||||||
|
Guardian -> AppGuardian : Buka GuardianGeofenceScreen
|
||||||
|
AppGuardian -> Backend : GET /api/v1/guardian/geofence
|
||||||
|
Backend --> AppGuardian : 200 OK {GeofenceResponse}
|
||||||
|
Guardian -> AppGuardian : Set center (tap peta),\nradius, enable
|
||||||
|
AppGuardian -> Backend : PUT /api/v1/guardian/geofence\n{centerLat, centerLng, radiusMeters, enabled}
|
||||||
|
Backend -> Backend : Update geofence_configs
|
||||||
|
Backend --> AppGuardian : 200 OK
|
||||||
|
AppGuardian -> Guardian : Tampilkan lingkaran\ngeofence di peta
|
||||||
|
|
||||||
|
Guardian -> AppGuardian : Buka GuardianVoiceCmdScreen
|
||||||
|
AppGuardian -> Backend : GET /api/v1/guardian/voice-commands
|
||||||
|
Backend --> AppGuardian : 200 OK List<VoiceCommandResponse>
|
||||||
|
Guardian -> AppGuardian : Ubah trigger phrase\n(misal: "mulai jalan")
|
||||||
|
AppGuardian -> Backend : PUT /api/v1/guardian/voice-commands\n{commandKey, triggerPhrase, enabled}
|
||||||
|
Backend -> Backend : Update voice_command_configs
|
||||||
|
Backend -> FCM : Push notif ke User\n(type=SETTINGS_UPDATED)
|
||||||
|
FCM --> AppUser : AppUser reload CachedVoiceCommands\nke SQLite lokal
|
||||||
|
Backend --> AppGuardian : 200 OK
|
||||||
|
|
||||||
|
Guardian -> AppGuardian : Buka GuardianActivityLogScreen
|
||||||
|
AppGuardian -> Backend : GET /api/v1/guardian/activity-logs?page=0&size=20
|
||||||
|
Backend --> AppGuardian : 200 OK Page<ActivityLogResponse>
|
||||||
|
AppGuardian -> Backend : GET /api/v1/guardian/obstacle-logs?page=0&size=20
|
||||||
|
Backend --> AppGuardian : 200 OK Page<ObstacleLogResponse>
|
||||||
|
AppGuardian -> Guardian : Tampilkan riwayat\naktivitas & deteksi obstacle
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' 12. LOGOUT
|
||||||
|
' ============================================================
|
||||||
|
== 12. Logout ==
|
||||||
|
|
||||||
|
User -> AppUser : Tap Logout
|
||||||
|
AppUser -> Backend : POST /api/v1/auth/logout
|
||||||
|
Backend -> Backend : Delete refresh token dari DB
|
||||||
|
Backend -> Backend : Log ActivityLog: LOGOUT
|
||||||
|
Backend --> AppUser : 200 OK
|
||||||
|
AppUser -> WS : disconnect()
|
||||||
|
AppUser -> AppUser : SecureStorage.clearAll()
|
||||||
|
AppUser -> AppUser : SharedPreferences: hapus token
|
||||||
|
AppUser -> User : Redirect ke /login
|
||||||
|
|
||||||
|
@enduml
|
||||||
470
ooad-docs/diagrams/state-machine.puml
Normal file
470
ooad-docs/diagrams/state-machine.puml
Normal file
@ -0,0 +1,470 @@
|
|||||||
|
@startuml WalkGuide_StateMachine
|
||||||
|
title State Machine Diagram — WalkGuide System (Full Architecture)
|
||||||
|
|
||||||
|
skinparam state {
|
||||||
|
BackgroundColor WhiteSmoke
|
||||||
|
BorderColor #5B6AB0
|
||||||
|
FontColor Black
|
||||||
|
ArrowColor #5B6AB0
|
||||||
|
StartColor Black
|
||||||
|
EndColor Black
|
||||||
|
}
|
||||||
|
skinparam note {
|
||||||
|
BackgroundColor LightYellow
|
||||||
|
BorderColor #999
|
||||||
|
}
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' TOP LEVEL: APP LAUNCH
|
||||||
|
' ============================================================
|
||||||
|
[*] --> AppLaunched : App dibuka
|
||||||
|
|
||||||
|
state AppLaunched {
|
||||||
|
[*] --> CheckingServerUrl
|
||||||
|
|
||||||
|
CheckingServerUrl : Cek SharedPreferences\nserver URL tersimpan?
|
||||||
|
|
||||||
|
CheckingServerUrl --> ServerConnectScreen : [Tidak ada URL]
|
||||||
|
CheckingServerUrl --> CheckingToken : [URL tersedia]
|
||||||
|
|
||||||
|
ServerConnectScreen : Input server URL\n(http://202.46.28.160:8080)
|
||||||
|
ServerConnectScreen --> PingingServer : Submit URL
|
||||||
|
|
||||||
|
PingingServer : GET /api/v1/auth/ping\n(cek koneksi ke backend)
|
||||||
|
PingingServer --> ServerConnectScreen : [Ping Gagal / Timeout]
|
||||||
|
PingingServer --> CheckingToken : [Ping OK → simpan URL ke SharedPreferences]
|
||||||
|
|
||||||
|
CheckingToken : Cek flutter_secure_storage\naccessToken ada?
|
||||||
|
CheckingToken --> Unauthenticated : [Token Tidak Ada]
|
||||||
|
CheckingToken --> ValidatingToken : [Token Ada]
|
||||||
|
|
||||||
|
ValidatingToken : Verifikasi JWT\n(decode expiry lokal)
|
||||||
|
ValidatingToken --> Unauthenticated : [Token Expired / Invalid]
|
||||||
|
ValidatingToken --> Authenticated : [Token Valid → decode role]
|
||||||
|
}
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' UNAUTHENTICATED
|
||||||
|
' ============================================================
|
||||||
|
state Unauthenticated {
|
||||||
|
[*] --> LoginScreen
|
||||||
|
|
||||||
|
LoginScreen : Input email & password\n+ tombol ke RegisterScreen
|
||||||
|
|
||||||
|
LoginScreen --> RegisterScreen : Tap "Register"
|
||||||
|
RegisterScreen --> LoginScreen : Tap "Back to Login"
|
||||||
|
|
||||||
|
RegisterScreen : Pilih role (ROLE_GUARDIAN / ROLE_USER)\nIsi email, password, displayName
|
||||||
|
|
||||||
|
LoginScreen --> ValidatingCredentials : Submit login
|
||||||
|
RegisterScreen --> RegisteringUser : Submit register
|
||||||
|
|
||||||
|
ValidatingCredentials : POST /api/v1/auth/login\n{email, password}
|
||||||
|
ValidatingCredentials --> LoginScreen : [401 Unauthorized → tampil error]
|
||||||
|
ValidatingCredentials --> StoringAuthData : [200 OK + JWT]
|
||||||
|
|
||||||
|
RegisteringUser : POST /api/v1/auth/register\n{email, password, role, displayName}
|
||||||
|
RegisteringUser --> LoginScreen : [400 / Email sudah terdaftar]
|
||||||
|
RegisteringUser --> StoringAuthData : [201 Created + JWT]
|
||||||
|
|
||||||
|
StoringAuthData : Simpan accessToken + refreshToken\nke flutter_secure_storage\nUpdate FCM token → PUT /auth/fcm-token\nWebSocketService.connect(accessToken)
|
||||||
|
StoringAuthData --> DecodingRole : JWT tersimpan
|
||||||
|
|
||||||
|
DecodingRole : Ekstrak role dari JWT claims
|
||||||
|
DecodingRole --> [*]
|
||||||
|
}
|
||||||
|
|
||||||
|
note on link
|
||||||
|
Login Sukses
|
||||||
|
200 OK + JWT
|
||||||
|
(access + refresh)
|
||||||
|
end note
|
||||||
|
|
||||||
|
Unauthenticated --> Authenticated : [Login / Register Sukses → role decoded]
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' AUTHENTICATED (role router)
|
||||||
|
' ============================================================
|
||||||
|
state Authenticated {
|
||||||
|
[*] --> CheckingPairing
|
||||||
|
|
||||||
|
CheckingPairing : GET /shared/pairing/status\ncek status pairing user ini
|
||||||
|
|
||||||
|
CheckingPairing --> UserShell : [Role = ROLE_USER]
|
||||||
|
CheckingPairing --> GuardianShell : [Role = ROLE_GUARDIAN]
|
||||||
|
}
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' USER SHELL
|
||||||
|
' ============================================================
|
||||||
|
state UserShell {
|
||||||
|
|
||||||
|
[*] --> UserIdle
|
||||||
|
|
||||||
|
UserIdle : Bottom nav 6 tabs aktif\nSTT always-listening di background\nWebSocket subscribe /queue/notif/{userId}
|
||||||
|
note right of UserIdle
|
||||||
|
Voice commands aktif:\n"Start Walkguide", "Open SOS",\n"Call Guardian", "Where Am I", dll
|
||||||
|
end note
|
||||||
|
|
||||||
|
' --- PAIRING CHECK ---
|
||||||
|
UserIdle --> PairingScreen_User : [Belum paired → TTS peringatan]
|
||||||
|
|
||||||
|
state PairingScreen_User {
|
||||||
|
[*] --> WaitingPairingInvite
|
||||||
|
WaitingPairingInvite : GET /shared/pairing/status\nTampil uniqueUserId 12-char\n"Share this ID with your Guardian"
|
||||||
|
WaitingPairingInvite --> ReceivingInvite : FCM: PAIRING_INVITE diterima
|
||||||
|
ReceivingInvite --> WaitingPairingInvite : Tap "Reject" → POST /pairing/respond {accept:false}
|
||||||
|
ReceivingInvite --> PairingAccepted : Tap "Accept" → POST /pairing/respond {accept:true}
|
||||||
|
PairingAccepted : Backend seed 14 VoiceCommands\n+ 5 Shortcuts + AiConfig defaults\nFCM ke Guardian: "Pairing accepted"\nTTS: "Pairing successful. Guardian connected."
|
||||||
|
PairingAccepted --> [*]
|
||||||
|
}
|
||||||
|
|
||||||
|
PairingScreen_User --> UserIdle : Pairing berhasil / lewati
|
||||||
|
|
||||||
|
' --- WALKGUIDE ---
|
||||||
|
UserIdle --> WalkGuideActive : Tap "Start Walk Guide"\nATAU voice "Start Walkguide"\nATAU Volume Down button
|
||||||
|
|
||||||
|
state WalkGuideActive {
|
||||||
|
[*] --> InitializingCamera
|
||||||
|
|
||||||
|
InitializingCamera : camera.startImageStream()\nRequest permissions (kamera, lokasi)\nPOST /user/walkguide/start → ActivityLog: WALKGUIDE_START
|
||||||
|
InitializingCamera --> [*] : [Permission Denied → kembali ke UserIdle]
|
||||||
|
|
||||||
|
InitializingCamera --> DetectionLoop : Kamera siap\nTTS: "3... 2... 1... WalkGuide started."
|
||||||
|
|
||||||
|
state DetectionLoop {
|
||||||
|
[*] --> CapturingFrame
|
||||||
|
|
||||||
|
CapturingFrame : Terima CameraImage (YUV420)\nThrottle: maxInferenceFps (default 5fps)
|
||||||
|
CapturingFrame --> ProcessingFrame : [YoloDetector tidak sedang running → kirim frame]
|
||||||
|
CapturingFrame --> CapturingFrame : [YoloDetector sedang running → skip frame ini]
|
||||||
|
|
||||||
|
ProcessingFrame : YUV420→RGB→Resize 640×640\nNormalize 0.0-1.0\ninterpreter.run() [Isolate terpisah]\nPost-process + NMS\nFilter by confidenceThreshold
|
||||||
|
|
||||||
|
ProcessingFrame --> EvaluatingResult : Inference selesai
|
||||||
|
|
||||||
|
EvaluatingResult : ObstacleAnalyzer.prioritize(results)\nTentukan direction (LEFT/CENTER/RIGHT)\nEstimasi distance (Very Close/Close/Medium/Far)
|
||||||
|
|
||||||
|
EvaluatingResult --> ObstacleDetected : [Confidence ≥ threshold & ada deteksi]
|
||||||
|
EvaluatingResult --> NoObstacleDetected : [Confidence < threshold / tidak ada deteksi]
|
||||||
|
|
||||||
|
ObstacleDetected : Build TTS: "Caution! {label} {direction}. {distance}. Please stop."\nttsService.speakImmediate()\nhapticService.obstacleVeryClose()\nDebounce 3 detik (jangan log yang sama terus)\nPOST /user/obstacle {label, confidence, direction, dist, lat, lng}
|
||||||
|
|
||||||
|
NoObstacleDetected : Lanjut deteksi
|
||||||
|
|
||||||
|
ObstacleDetected --> CapturingFrame : Feedback selesai
|
||||||
|
NoObstacleDetected --> CapturingFrame : Lanjut deteksi
|
||||||
|
|
||||||
|
' Location update loop (parallel, setiap 5 detik)
|
||||||
|
CapturingFrame --> SendingLocation : [Setiap 5 detik]
|
||||||
|
SendingLocation : geolocator.getCurrentPosition()\nPOST /user/location {lat, lng, accuracy, speed, heading}\nBackend: simpan LocationHistory\nWebSocket broadcast → Guardian map\nGeofenceService.checkGeofence()
|
||||||
|
SendingLocation --> CapturingFrame : Location terkirim
|
||||||
|
}
|
||||||
|
|
||||||
|
DetectionLoop --> SavingSession : Tap "Stop Walk Guide"\nATAU voice "Stop Walkguide"
|
||||||
|
|
||||||
|
SavingSession : camera.stopImageStream()\nPOST /user/walkguide/stop → ActivityLog: WALKGUIDE_STOP\nLocation interval → 30 detik
|
||||||
|
SavingSession --> [*]
|
||||||
|
}
|
||||||
|
|
||||||
|
WalkGuideActive --> UserIdle : Sesi WalkGuide selesai
|
||||||
|
|
||||||
|
' --- SOS ---
|
||||||
|
UserIdle --> SosFlow : Tap SOS tab\nATAU voice "Send SOS"\nATAU hardware shortcut
|
||||||
|
|
||||||
|
state SosFlow {
|
||||||
|
[*] --> SosConfirm
|
||||||
|
SosConfirm : SosScreen: tombol SOS merah besar\nTTS: "SOS screen. Press the big red button\nor say 'Send SOS' to alert your Guardian."
|
||||||
|
SosConfirm --> TriggeringSos : Tap SOS button / voice "Send SOS"
|
||||||
|
TriggeringSos : Get GPS position\nPOST /user/sos {triggerType, lat, lng}\nBackend: save SosEvent TRIGGERED\nFCM HIGH PRIORITY ke Guardian: "🚨 SOS ALERT!"\nWebSocket → /queue/sos/{guardianId}\nTTS: "SOS sent. Guardian has been alerted."\nhapticService.sosTriggered()
|
||||||
|
TriggeringSos --> WaitingAcknowledge : SOS terkirim
|
||||||
|
WaitingAcknowledge : Tunggu respons Guardian\nTampil status "Waiting for Guardian..."
|
||||||
|
WaitingAcknowledge --> SosAcknowledged : FCM: Guardian acknowledge SOS\nTTS: "Your Guardian has acknowledged.\nHelp is coming."
|
||||||
|
SosAcknowledged --> [*]
|
||||||
|
}
|
||||||
|
|
||||||
|
SosFlow --> UserIdle : Kembali ke home
|
||||||
|
|
||||||
|
' --- NOTIFICATIONS ---
|
||||||
|
UserIdle --> NotificationFlow : Tap Notifications tab\nATAU voice "Open Notifications"\nATAU FCM/WebSocket masuk
|
||||||
|
|
||||||
|
state NotificationFlow {
|
||||||
|
[*] --> ViewingNotifications
|
||||||
|
ViewingNotifications : GET /user/notifications\nGET /user/notifications/unread-count\nTampil list: TEXT / VOICE_NOTE\nBadge unread count\nTTS: "Notifications. You have {N} unread messages."
|
||||||
|
ViewingNotifications --> ReadingAllTts : Tap "Read All" / voice "Read All My Notifications"
|
||||||
|
ViewingNotifications --> ReadingOneTts : Tap satu notifikasi
|
||||||
|
ReadingAllTts : Iterate unread list:\n- TEXT: TTS "From Guardian on {date}: {content}"\n- VOICE_NOTE: just_audio.play(voiceNoteUrl)\nPUT /user/notifications/{id}/read setelah selesai
|
||||||
|
ReadingOneTts : TTS / play satu notifikasi\nPUT /user/notifications/{id}/read
|
||||||
|
ReadingAllTts --> ViewingNotifications : Semua notif dibaca
|
||||||
|
ReadingOneTts --> ViewingNotifications : Selesai
|
||||||
|
ViewingNotifications --> MarkingAllRead : Tap "Mark All Read"\nPUT /user/notifications/mark-all-read
|
||||||
|
MarkingAllRead --> ViewingNotifications : Done
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationFlow --> UserIdle : Kembali
|
||||||
|
|
||||||
|
' --- NAVIGATION MODE ---
|
||||||
|
UserIdle --> NavigationModeActive : Tap Navigate tab\nATAU voice "Open Navigation"
|
||||||
|
|
||||||
|
state NavigationModeActive {
|
||||||
|
[*] --> NavigationIdle
|
||||||
|
NavigationIdle : FlutterMap (OpenStreetMap tiles)\nTTS: "Navigation mode. Say a destination."
|
||||||
|
NavigationIdle --> SearchingDestination : Tap search bar / voice "Navigate to {place}"
|
||||||
|
SearchingDestination : OSM Nominatim API\nGET nominatim.openstreetmap.org/search?q=...
|
||||||
|
SearchingDestination --> NavigatingRoute : Lokasi ditemukan → OSRM routing
|
||||||
|
NavigatingRoute : OSRM turn-by-turn route\nTTS setiap instruksi: "In 50 meters, turn right"\ngeolocator: GPS real-time\nPOST /user/location setiap 5 detik
|
||||||
|
NavigatingRoute --> NavigationIdle : Sampai tujuan / Stop
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationModeActive --> UserIdle : Kembali
|
||||||
|
|
||||||
|
' --- CALL ---
|
||||||
|
UserIdle --> OutgoingCallFlow : Tap "Call Guardian"\nATAU voice "Call Guardian"\nATAU Volume Up button
|
||||||
|
|
||||||
|
state OutgoingCallFlow {
|
||||||
|
[*] --> RequestingAgoraToken
|
||||||
|
RequestingAgoraToken : POST /shared/call/token\nGenerate Agora RTC token\nPOST /shared/call/notify → FCM ke Guardian "Incoming Call"
|
||||||
|
RequestingAgoraToken --> CallingGuardian : Token dapat, join Agora channel
|
||||||
|
CallingGuardian : CallScreen: "Calling Guardian..." + animasi\nAgoraRtcEngine.joinChannel()
|
||||||
|
CallingGuardian --> InCall : Guardian pick up → CallConnected
|
||||||
|
CallingGuardian --> UserIdle : Guardian decline / timeout 30 detik
|
||||||
|
InCall : Timer durasi call\nTombol: Mute, Speaker, End Call\nTTS: "Connected to Guardian"
|
||||||
|
InCall --> UserIdle : Tap End Call
|
||||||
|
}
|
||||||
|
|
||||||
|
UserIdle --> IncomingCallHandled : FCM INCOMING_CALL diterima dari Guardian
|
||||||
|
|
||||||
|
state IncomingCallHandled {
|
||||||
|
[*] --> ShowingIncomingCall
|
||||||
|
ShowingIncomingCall : IncomingCallScreen\nTTS: "Incoming call from Guardian"\nHaptic vibration\nTombol Accept (hijau) + Decline (merah)\nAuto-answer setelah 30 detik
|
||||||
|
ShowingIncomingCall --> InCallSession : Accept
|
||||||
|
ShowingIncomingCall --> UserIdle : Decline
|
||||||
|
InCallSession : Agora RTC connected\nTimer + Mute + Speaker + End
|
||||||
|
InCallSession --> UserIdle : End call
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- SETTINGS ---
|
||||||
|
UserIdle --> UserSettingsFlow : Tap Settings tab\nATAU voice "Open Settings"
|
||||||
|
|
||||||
|
state UserSettingsFlow {
|
||||||
|
[*] --> ViewingSettings
|
||||||
|
ViewingSettings : TTS Settings (read-only pitch/speed)\nPairing status link\nManual/Instructions link\nNo-Guardian Warning toggle\nAccount info + Logout
|
||||||
|
ViewingSettings --> UpdatingTtsLanguage : Toggle ID/EN → PUT /user/settings
|
||||||
|
ViewingSettings --> ViewingManual : Tap "Instructions"
|
||||||
|
ViewingSettings --> LoggingOut : Tap Logout
|
||||||
|
UpdatingTtsLanguage --> ViewingSettings : Saved
|
||||||
|
ViewingManual : ManualScreen\nSemua voice commands + shortcuts\nTombol "Hear Instructions"\nTTS setiap instruksi 1-per-1
|
||||||
|
ViewingManual --> ViewingSettings : Back
|
||||||
|
LoggingOut : POST /auth/logout {refreshToken}\nSecureStorage.clearAll()\nWebSocketService.disconnect()\nGoRouter → /login
|
||||||
|
LoggingOut --> [*]
|
||||||
|
}
|
||||||
|
|
||||||
|
UserSettingsFlow --> UserIdle : Kembali
|
||||||
|
UserSettingsFlow --> [*] : Logout
|
||||||
|
|
||||||
|
' --- ACTIVITY LOG ---
|
||||||
|
UserIdle --> ActivityLogFlow : Tap Activity tab\nATAU voice "Open Activity Log"
|
||||||
|
|
||||||
|
state ActivityLogFlow {
|
||||||
|
[*] --> ViewingActivityLog
|
||||||
|
ViewingActivityLog : GET /user/activity-logs (paginated)\nInfinite scroll\nFilter by type/date\nTTS: "Activity log. {N} activities today."
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityLogFlow --> UserIdle : Kembali
|
||||||
|
}
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' GUARDIAN SHELL
|
||||||
|
' ============================================================
|
||||||
|
state GuardianShell {
|
||||||
|
|
||||||
|
[*] --> GuardianIdle
|
||||||
|
|
||||||
|
GuardianIdle : GuardianDashboardScreen HOME\nCard: User status (online/offline, battery)\nCard: Last location (Nominatim reverse)\nCard: Unread SOS (badge merah)\nCard: Recent 5 activity logs\nQuick actions: Send Message, Call User, View Map\nWebSocket subscribe:\n /topic/location/{pairedUserId}\n /queue/sos/{guardianId}
|
||||||
|
|
||||||
|
' --- PAIRING (Guardian side) ---
|
||||||
|
GuardianIdle --> GuardianPairingFlow : Tap "Manage Pairing"\nATAU belum punya paired user
|
||||||
|
|
||||||
|
state GuardianPairingFlow {
|
||||||
|
[*] --> EnteringUserId
|
||||||
|
EnteringUserId : Input uniqueUserId 12-char\nmilik paired User
|
||||||
|
EnteringUserId --> SendingInvite : Tap "Send Invite"
|
||||||
|
SendingInvite : POST /shared/pairing/invite {uniqueUserId}\nBackend: buat PairingRelation PENDING\nFCM ke User: "Pairing Request"
|
||||||
|
SendingInvite --> WaitingUserResponse : Invite terkirim
|
||||||
|
WaitingUserResponse : Tunggu User accept/reject
|
||||||
|
WaitingUserResponse --> PairingSuccess : FCM: User accepted\nBackend seed VoiceCommands + Shortcuts + AiConfig
|
||||||
|
WaitingUserResponse --> EnteringUserId : FCM: User rejected
|
||||||
|
PairingSuccess : TTS: "Pairing successful. User is now connected."\nNavigate → GuardianIdle
|
||||||
|
PairingSuccess --> [*]
|
||||||
|
}
|
||||||
|
|
||||||
|
GuardianPairingFlow --> GuardianIdle : Selesai
|
||||||
|
|
||||||
|
' --- MAP / LIVE TRACKING ---
|
||||||
|
GuardianIdle --> GuardianMapView : Tap "View Map"\nATAU quick action map
|
||||||
|
|
||||||
|
state GuardianMapView {
|
||||||
|
[*] --> ViewingLiveMap
|
||||||
|
ViewingLiveMap : FlutterMap + live location marker\nWebSocket /topic/location/{userId}\nGeofence circle overlay (jika enabled)\nRoute history polyline (location_history)
|
||||||
|
ViewingLiveMap --> ViewingLiveMap : [WebSocket update → marker pindah]
|
||||||
|
}
|
||||||
|
|
||||||
|
GuardianMapView --> GuardianIdle : Kembali
|
||||||
|
|
||||||
|
' --- ACTIVITY LOGS ---
|
||||||
|
GuardianIdle --> GuardianActivityLog : Tap "View Activity Logs"
|
||||||
|
|
||||||
|
state GuardianActivityLog {
|
||||||
|
[*] --> ViewingUserLogs
|
||||||
|
ViewingUserLogs : GET /guardian/activity-logs (paginated)\nGET /guardian/obstacle-logs\nFilter by type, date
|
||||||
|
}
|
||||||
|
|
||||||
|
GuardianActivityLog --> GuardianIdle : Kembali
|
||||||
|
|
||||||
|
' --- SEND NOTIFICATION ---
|
||||||
|
GuardianIdle --> GuardianSendNotif : Tap "Send Notification"
|
||||||
|
|
||||||
|
state GuardianSendNotif {
|
||||||
|
[*] --> ComposingNotif
|
||||||
|
ComposingNotif : Toggle: TEXT / VOICE NOTE
|
||||||
|
ComposingNotif --> SendingTextNotif : Ketik pesan → Send
|
||||||
|
ComposingNotif --> RecordingVoiceNote : Tahan tombol Record
|
||||||
|
SendingTextNotif : POST /guardian/notifications/send\n{type: TEXT, content}\nBackend: FCM ke User + WebSocket\nUser TTS: "New message from Guardian"
|
||||||
|
RecordingVoiceNote : record package → audio file\nPreview → Upload → POST send\n{type: VOICE_NOTE, voiceNoteUrl}
|
||||||
|
SendingTextNotif --> ComposingNotif : Sent
|
||||||
|
RecordingVoiceNote --> ComposingNotif : Sent
|
||||||
|
}
|
||||||
|
|
||||||
|
GuardianSendNotif --> GuardianIdle : Kembali
|
||||||
|
|
||||||
|
' --- AI CONFIG ---
|
||||||
|
GuardianIdle --> GuardianAiConfig : Tap "Configure AI Settings"
|
||||||
|
|
||||||
|
state GuardianAiConfig {
|
||||||
|
[*] --> EditingAiConfig
|
||||||
|
EditingAiConfig : GET /guardian/ai-config\nSlider confidenceThreshold (0.3-0.9)\nSlider alertDistanceClose (0.5-3m)\nSlider alertDistanceMedium (1-6m)\nDropdown maxInferenceFps (1,2,3,5,10)\nMulti-select enabled labels
|
||||||
|
EditingAiConfig --> SavingAiConfig : Tap Save
|
||||||
|
SavingAiConfig : PUT /guardian/ai-config\nBackend: update + FCM ke User\n"Guardian updated AI settings"
|
||||||
|
SavingAiConfig --> EditingAiConfig : Saved
|
||||||
|
}
|
||||||
|
|
||||||
|
GuardianAiConfig --> GuardianIdle : Kembali
|
||||||
|
|
||||||
|
' --- VOICE COMMANDS CONFIG ---
|
||||||
|
GuardianIdle --> GuardianVoiceCmdConfig : Tap "Voice Commands"
|
||||||
|
|
||||||
|
state GuardianVoiceCmdConfig {
|
||||||
|
[*] --> EditingVoiceCommands
|
||||||
|
EditingVoiceCommands : GET /guardian/voice-commands\nList 14 commands\nEdit trigger phrase\nToggle enabled/disabled
|
||||||
|
EditingVoiceCommands --> SavingVoiceCmd : Save
|
||||||
|
SavingVoiceCmd : PUT /guardian/voice-commands\nFCM ke User: "Voice commands updated"
|
||||||
|
SavingVoiceCmd --> EditingVoiceCommands : Saved
|
||||||
|
}
|
||||||
|
|
||||||
|
GuardianVoiceCmdConfig --> GuardianIdle : Kembali
|
||||||
|
|
||||||
|
' --- HARDWARE SHORTCUTS ---
|
||||||
|
GuardianIdle --> GuardianShortcutConfig : Tap "Shortcuts"
|
||||||
|
|
||||||
|
state GuardianShortcutConfig {
|
||||||
|
[*] --> EditingShortcuts
|
||||||
|
EditingShortcuts : GET /guardian/shortcuts\nList 5 shortcuts (Vol Up, Vol Down, dll)\n"Unassigned" jika belum di-set
|
||||||
|
EditingShortcuts --> CapturingButton : Tap "Configure" per shortcut
|
||||||
|
CapturingButton : FCM ke User's phone "enter capture mode"\nUser tekan tombol fisik → keyCode\nPUT /user/shortcuts {shortcutKey, buttonCode}
|
||||||
|
CapturingButton --> EditingShortcuts : Button captured & saved
|
||||||
|
}
|
||||||
|
|
||||||
|
GuardianShortcutConfig --> GuardianIdle : Kembali
|
||||||
|
|
||||||
|
' --- GEOFENCE ---
|
||||||
|
GuardianIdle --> GuardianGeofenceConfig : Tap "Geofence"
|
||||||
|
|
||||||
|
state GuardianGeofenceConfig {
|
||||||
|
[*] --> EditingGeofence
|
||||||
|
EditingGeofence : FlutterMap: tap untuk set center\nSlider radius (50m - 5000m)\nToggle enable/disable geofence
|
||||||
|
EditingGeofence --> SavingGeofence : Tap Save
|
||||||
|
SavingGeofence : PUT /guardian/geofence\nBackend: simpan GeofenceConfig
|
||||||
|
SavingGeofence --> EditingGeofence : Saved
|
||||||
|
}
|
||||||
|
|
||||||
|
GuardianGeofenceConfig --> GuardianIdle : Kembali
|
||||||
|
|
||||||
|
' --- SOS HANDLING ---
|
||||||
|
GuardianIdle --> SosAlertReceived : FCM HIGH PRIORITY / WebSocket SOS alert\n/queue/sos/{guardianId}
|
||||||
|
|
||||||
|
state SosAlertReceived {
|
||||||
|
[*] --> DisplayingSosAlert
|
||||||
|
DisplayingSosAlert : GuardianDashboard: highlight merah\nMap otomatis buka ke lokasi User\nTampil: "🚨 {User} needs help! Location: lat,lng"\nGET /guardian/sos-events
|
||||||
|
DisplayingSosAlert --> AcknowledgingSos : Tap "Acknowledge"
|
||||||
|
AcknowledgingSos : PUT /guardian/sos/{id}/acknowledge\nBackend: status → ACKNOWLEDGED\nFCM ke User: "Guardian is on the way"\nUser TTS: "Help is coming."
|
||||||
|
AcknowledgingSos --> [*]
|
||||||
|
}
|
||||||
|
|
||||||
|
SosAlertReceived --> GuardianIdle : Selesai
|
||||||
|
|
||||||
|
' --- CALL (Guardian inisiasi) ---
|
||||||
|
GuardianIdle --> GuardianOutgoingCall : Tap "Call User"
|
||||||
|
|
||||||
|
state GuardianOutgoingCall {
|
||||||
|
[*] --> GuardianRequestToken
|
||||||
|
GuardianRequestToken : POST /shared/call/token\nPOST /shared/call/notify → FCM ke User "Incoming Call"
|
||||||
|
GuardianRequestToken --> GuardianCalling : Join Agora channel
|
||||||
|
GuardianCalling : "Calling User..." + ring animasi
|
||||||
|
GuardianCalling --> GuardianInCall : User accept
|
||||||
|
GuardianCalling --> GuardianIdle : User decline / timeout
|
||||||
|
GuardianInCall : Audio call aktif\nMute + Speaker + End
|
||||||
|
GuardianInCall --> GuardianIdle : End call
|
||||||
|
}
|
||||||
|
|
||||||
|
GuardianOutgoingCall --> GuardianIdle : Selesai
|
||||||
|
|
||||||
|
' --- INCOMING CALL (Guardian terima) ---
|
||||||
|
GuardianIdle --> GuardianIncomingCall : FCM INCOMING_CALL dari User
|
||||||
|
|
||||||
|
state GuardianIncomingCall {
|
||||||
|
[*] --> GuardianShowIncomingCall
|
||||||
|
GuardianShowIncomingCall : IncomingCallScreen\nTTS: "Incoming call from User"\nAccept (hijau) / Decline (merah)
|
||||||
|
GuardianShowIncomingCall --> GuardianInCallSession : Accept
|
||||||
|
GuardianShowIncomingCall --> GuardianIdle : Decline
|
||||||
|
GuardianInCallSession : Agora RTC connected
|
||||||
|
GuardianInCallSession --> GuardianIdle : End call
|
||||||
|
}
|
||||||
|
|
||||||
|
GuardianIncomingCall --> GuardianIdle : Selesai
|
||||||
|
|
||||||
|
' --- LOGOUT ---
|
||||||
|
GuardianIdle --> GuardianLoggingOut : Tap Logout
|
||||||
|
|
||||||
|
GuardianLoggingOut : POST /auth/logout {refreshToken}\nSecureStorage.clearAll()\nWebSocket.disconnect()
|
||||||
|
GuardianLoggingOut --> [*]
|
||||||
|
}
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' GLOBAL LOGOUT / TOKEN REFRESH
|
||||||
|
' ============================================================
|
||||||
|
UserShell --> Unauthenticated : Logout / Token Expired (401 → refresh gagal)
|
||||||
|
GuardianShell --> Unauthenticated : Logout / Token Expired (401 → refresh gagal)
|
||||||
|
|
||||||
|
note as TokenRefreshNote
|
||||||
|
**Token Refresh (AuthInterceptor):**
|
||||||
|
Setiap request: inject "Authorization: Bearer {accessToken}"
|
||||||
|
Jika 401 → POST /auth/refresh {refreshToken}
|
||||||
|
Jika refresh OK → retry request dengan token baru
|
||||||
|
Jika refresh 401 → navigate ke /login
|
||||||
|
Access token: 1 jam | Refresh token: 30 hari
|
||||||
|
end note
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' GEOFENCE EXIT (Parallel event)
|
||||||
|
' ============================================================
|
||||||
|
note as GeofenceNote
|
||||||
|
**Geofence Alert (Backend event):**
|
||||||
|
Setiap POST /user/location (saat WalkGuide aktif, 5 detik):
|
||||||
|
Backend: GeofenceService.checkGeofence()
|
||||||
|
→ Haversine distance dari center ke current position
|
||||||
|
→ Jika keluar radius: FCM ke Guardian "User has left geofence!"
|
||||||
|
→ ActivityLog: GEOFENCE_EXIT
|
||||||
|
→ Guardian: notifikasi di GuardianDashboard
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
176
ooad-docs/diagrams/usecase-diagram.puml
Normal file
176
ooad-docs/diagrams/usecase-diagram.puml
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
@startuml
|
||||||
|
left to right direction
|
||||||
|
skinparam packageStyle rectangle
|
||||||
|
skinparam rectangle {
|
||||||
|
BackgroundColor<<actor>> LightGray
|
||||||
|
BorderColor Black
|
||||||
|
}
|
||||||
|
|
||||||
|
' ==========================================
|
||||||
|
' AKTOR UTAMA (Kiri)
|
||||||
|
' ==========================================
|
||||||
|
actor "Tunanetra\n(ROLE_USER)" as User
|
||||||
|
actor "Pendamping\n(ROLE_GUARDIAN)" as Guardian
|
||||||
|
|
||||||
|
' ==========================================
|
||||||
|
' BATAS SISTEM (Tengah)
|
||||||
|
' ==========================================
|
||||||
|
rectangle "WalkGuide System" {
|
||||||
|
|
||||||
|
' --- AUTH ---
|
||||||
|
usecase "Hubungkan ke Server" as UC_Server
|
||||||
|
usecase "Daftar Akun" as UC_Register
|
||||||
|
usecase "Masuk Sistem (Login)" as UC_Login
|
||||||
|
usecase "Logout" as UC_Logout
|
||||||
|
usecase "Validasi & Refresh JWT" as UC_JWT
|
||||||
|
usecase "RBAC Role Routing" as UC_RBAC
|
||||||
|
|
||||||
|
' --- PAIRING ---
|
||||||
|
usecase "Kirim Undangan Pairing" as UC_Invite
|
||||||
|
usecase "Terima / Tolak Pairing" as UC_PairingResponse
|
||||||
|
usecase "Putus Pairing (Unpair)" as UC_Unpair
|
||||||
|
|
||||||
|
' --- WALKGUIDE CORE ---
|
||||||
|
usecase "Aktifkan Walk Guide Mode" as UC_Walk
|
||||||
|
usecase "Deteksi Obstacle\nReal-time (YOLO)" as UC_Detect
|
||||||
|
usecase "Analisa Arah & Jarak\nObstacle" as UC_Analyze
|
||||||
|
usecase "Umpan Balik TTS (Audio)" as UC_TTS
|
||||||
|
usecase "Umpan Balik Vibrasi\n(Haptic)" as UC_Vib
|
||||||
|
usecase "Log Obstacle ke Backend" as UC_ObsLog
|
||||||
|
|
||||||
|
' --- LOKASI ---
|
||||||
|
usecase "Kirim Lokasi GPS\nReal-time (WebSocket)" as UC_Location
|
||||||
|
usecase "Mode Navigasi\n(OpenStreetMap)" as UC_Navigation
|
||||||
|
usecase "\"Where Am I?\"\n(Reverse Geocode)" as UC_WhereAmI
|
||||||
|
|
||||||
|
' --- VOICE & SHORTCUT ---
|
||||||
|
usecase "Perintah Suara\n(Speech-to-Text)" as UC_Voice
|
||||||
|
usecase "Shortcut Tombol\nFisik Hardware" as UC_Hardware
|
||||||
|
|
||||||
|
' --- SOS & DARURAT ---
|
||||||
|
usecase "Kirim SOS (Darurat)" as UC_SOS
|
||||||
|
usecase "Panggil Guardian\n(VoIP Agora)" as UC_Call
|
||||||
|
usecase "Terima Panggilan Masuk" as UC_IncomingCall
|
||||||
|
|
||||||
|
' --- NOTIFIKASI ---
|
||||||
|
usecase "Terima Notifikasi FCM" as UC_FCMReceive
|
||||||
|
usecase "Kirim Notifikasi\nTeks / Voice Note" as UC_SendNotif
|
||||||
|
usecase "Baca Notifikasi via TTS" as UC_ReadNotif
|
||||||
|
usecase "Tandai Notifikasi\nSudah Dibaca" as UC_MarkRead
|
||||||
|
|
||||||
|
' --- LOG ---
|
||||||
|
usecase "Lihat Log Aktivitas" as UC_ViewLog
|
||||||
|
usecase "Lihat Log Obstacle" as UC_ViewObsLog
|
||||||
|
|
||||||
|
' --- PENGATURAN ---
|
||||||
|
usecase "Atur Preferensi TTS\n& Haptic" as UC_UserSettings
|
||||||
|
usecase "Kelola Perintah\nSuara Kustom" as UC_VoiceConfig
|
||||||
|
usecase "Kelola Shortcut\nTombol Fisik" as UC_ShortcutConfig
|
||||||
|
|
||||||
|
' --- GUARDIAN DASHBOARD ---
|
||||||
|
usecase "Monitor Lokasi User\nReal-time (Map)" as UC_MonitorMap
|
||||||
|
usecase "Kelola Konfigurasi AI\n(Threshold YOLO)" as UC_AIConfig
|
||||||
|
usecase "Kelola Geofence\n(Radius Aman)" as UC_Geofence
|
||||||
|
usecase "Kelola Akun\nUser Paired" as UC_ManageUser
|
||||||
|
usecase "Konfirmasi/Acknowledge\nSOS" as UC_AckSOS
|
||||||
|
}
|
||||||
|
|
||||||
|
' ==========================================
|
||||||
|
' AKTOR SISTEM (Kanan)
|
||||||
|
' ==========================================
|
||||||
|
rectangle "YOLOv8n\n(On-Device AI)" as AI <<actor>>
|
||||||
|
rectangle "Spring Boot\nBackend" as Backend <<actor>>
|
||||||
|
rectangle "Firebase FCM" as FCM <<actor>>
|
||||||
|
rectangle "Agora RTC" as Agora <<actor>>
|
||||||
|
|
||||||
|
' ==========================================
|
||||||
|
' RELASI USER (ROLE_USER)
|
||||||
|
' ==========================================
|
||||||
|
User "*" -- "*" UC_Server
|
||||||
|
User "*" -- "*" UC_Login
|
||||||
|
User "*" -- "*" UC_Register
|
||||||
|
User "*" -- "*" UC_Logout
|
||||||
|
User "*" -- "*" UC_PairingResponse
|
||||||
|
User "*" -- "*" UC_Unpair
|
||||||
|
User "*" -- "*" UC_Walk
|
||||||
|
User "*" -- "*" UC_Voice
|
||||||
|
User "*" -- "*" UC_Hardware
|
||||||
|
User "*" -- "*" UC_SOS
|
||||||
|
User "*" -- "*" UC_Call
|
||||||
|
User "*" -- "*" UC_IncomingCall
|
||||||
|
User "*" -- "*" UC_Navigation
|
||||||
|
User "*" -- "*" UC_WhereAmI
|
||||||
|
User "*" -- "*" UC_FCMReceive
|
||||||
|
User "*" -- "*" UC_ReadNotif
|
||||||
|
User "*" -- "*" UC_MarkRead
|
||||||
|
User "*" -- "*" UC_ViewLog
|
||||||
|
User "*" -- "*" UC_UserSettings
|
||||||
|
User "*" -- "*" UC_ShortcutConfig
|
||||||
|
|
||||||
|
' ==========================================
|
||||||
|
' RELASI GUARDIAN (ROLE_GUARDIAN)
|
||||||
|
' ==========================================
|
||||||
|
Guardian "*" -- "*" UC_Login
|
||||||
|
Guardian "*" -- "*" UC_Register
|
||||||
|
Guardian "*" -- "*" UC_Logout
|
||||||
|
Guardian "*" -- "*" UC_Invite
|
||||||
|
Guardian "*" -- "*" UC_Unpair
|
||||||
|
Guardian "*" -- "*" UC_MonitorMap
|
||||||
|
Guardian "*" -- "*" UC_SendNotif
|
||||||
|
Guardian "*" -- "*" UC_AIConfig
|
||||||
|
Guardian "*" -- "*" UC_Geofence
|
||||||
|
Guardian "*" -- "*" UC_VoiceConfig
|
||||||
|
Guardian "*" -- "*" UC_ShortcutConfig
|
||||||
|
Guardian "*" -- "*" UC_ManageUser
|
||||||
|
Guardian "*" -- "*" UC_IncomingCall
|
||||||
|
Guardian "*" -- "*" UC_ViewLog
|
||||||
|
Guardian "*" -- "*" UC_ViewObsLog
|
||||||
|
Guardian "*" -- "*" UC_Call
|
||||||
|
Guardian "*" -- "*" UC_AckSOS
|
||||||
|
|
||||||
|
' ==========================================
|
||||||
|
' INCLUDE & EXTEND
|
||||||
|
' ==========================================
|
||||||
|
UC_Login ..> UC_JWT : <<include>>
|
||||||
|
UC_Register ..> UC_JWT : <<include>>
|
||||||
|
UC_JWT ..> UC_RBAC : <<include>>
|
||||||
|
|
||||||
|
UC_Walk ..> UC_Detect : <<include>>
|
||||||
|
UC_Detect ..> UC_Analyze : <<include>>
|
||||||
|
UC_Analyze <.. UC_TTS : <<extend>>
|
||||||
|
UC_Analyze <.. UC_Vib : <<extend>>
|
||||||
|
UC_Detect ..> UC_ObsLog : <<include>>
|
||||||
|
UC_Walk ..> UC_Location : <<include>>
|
||||||
|
|
||||||
|
UC_Walk <.. UC_Voice : <<extend>>
|
||||||
|
UC_SOS <.. UC_Voice : <<extend>>
|
||||||
|
UC_Call <.. UC_Voice : <<extend>>
|
||||||
|
|
||||||
|
UC_Walk <.. UC_Hardware : <<extend>>
|
||||||
|
UC_SOS <.. UC_Hardware : <<extend>>
|
||||||
|
UC_Call <.. UC_Hardware : <<extend>>
|
||||||
|
|
||||||
|
UC_SOS ..> UC_FCMReceive : <<include>>
|
||||||
|
UC_SendNotif ..> UC_FCMReceive : <<include>>
|
||||||
|
UC_FCMReceive <.. UC_TTS : <<extend>>
|
||||||
|
UC_Call <.. UC_IncomingCall : <<extend>>
|
||||||
|
UC_Geofence <.. UC_FCMReceive : <<extend>>
|
||||||
|
|
||||||
|
' ==========================================
|
||||||
|
' RELASI KE SISTEM EKSTERNAL
|
||||||
|
' ==========================================
|
||||||
|
UC_Detect "*" ----- "*" AI
|
||||||
|
UC_JWT "*" ----- "*" Backend
|
||||||
|
UC_ObsLog "*" ----- "*" Backend
|
||||||
|
UC_Location "*" ----- "*" Backend
|
||||||
|
UC_SOS "*" ----- "*" Backend
|
||||||
|
UC_SendNotif "*" ----- "*" Backend
|
||||||
|
UC_ViewLog "*" ----- "*" Backend
|
||||||
|
UC_ViewObsLog "*" ----- "*" Backend
|
||||||
|
UC_AIConfig "*" ----- "*" Backend
|
||||||
|
UC_FCMReceive "*" ----- "*" FCM
|
||||||
|
UC_SendNotif "*" ----- "*" FCM
|
||||||
|
UC_Call "*" ----- "*" Agora
|
||||||
|
UC_IncomingCall "*" ----- "*" Agora
|
||||||
|
|
||||||
|
@enduml
|
||||||
Loading…
x
Reference in New Issue
Block a user