체류 시간 로컬 로깅에 대해 진행하겠다
우선 backend 부터 시작했다
다음의 log_api를 만들었으며
from sqlalchemy.orm import Session
import models, schemas
from datetime import date, datetime, timezone, timedelta
# 한국 시간대 설정
KST = timezone(timedelta(hours=9))
# 새로운 로그 생성
def start_log(db: Session, wifi_log: schemas.WifiLogCreate):
db_wifi_log = models.WifiLog(
user_id=wifi_log.user_id,
start_time=datetime.now(KST)
)
db.add(db_wifi_log)
db.commit()
db.refresh(db_wifi_log)
return db_wifi_log
# 전체 사용자 목록
def get_all_logs(db: Session, start: int = 0, last: int = 10):
return db.query(models.WifiLog).offset(start).limit(last).all()
# 사용자 id로 사용자 반환
def get_log_by_id(db: Session, user_id: int):
return db.query(models.WifiLog).filter(models.WifiLog.user_id == user_id).first()
def end_log(db: Session, log_id: int):
db_log = db.query(models.WifiLog).filter(models.WifiLog.id == log_id).first()
if db_log is None:
return None
db_log.end_time = datetime.now(KST)
db.commit()
db.refresh(db_log)
return db_log
다음의 log api router이다
from fastapi import APIRouter
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from database import get_db
import schemas
from api import log_api
wifi_log = APIRouter(prefix="/log", tags=["log"])
@wifi_log.post("/start", response_model=schemas.WifiLog)
def start_log(wifi_log: schemas.WifiLogCreate, db: Session = Depends(get_db)):
return log_api.start_log(db=db, wifi_log=wifi_log)
@wifi_log.get("/id/{user_id}", response_model=schemas.WifiLog)
def read_log_by_id(user_id: int, db: Session = Depends(get_db)):
db_log = log_api.get_log_by_id(db, user_id=user_id)
if db_log is None:
raise HTTPException(status_code=404, detail="Log not found")
return db_log
@wifi_log.get("/list", response_model=List[schemas.WifiLog])
def read_logs(start: int = 0, last: int = 10, db: Session = Depends(get_db)):
logs = log_api.get_all_logs(db, start=start, last=last)
return logs
@wifi_log.post("/end", response_model=schemas.WifiLog)
def end_log(log_id: int, db: Session = Depends(get_db)):
db_log = log_api.end_log(db=db, log_id=log_id)
if db_log is None:
raise HTTPException(status_code=404, detail="Log not found")
return db_log
그리고 스키마도 다음과 같이 수정하였다
from datetime import datetime
from pydantic import BaseModel
# User 관련 스키마
class UserBase(BaseModel):
user_name: str
home_ssid: str
home_bssid: str
class UserCreate(UserBase):
pass
class User(UserBase):
id: int
class Config:
from_attributes = True
# WifiLog 관련 스키마
class WifiLogBase(BaseModel):
user_id: int
class WifiLogCreate(WifiLogBase):
pass
class WifiLog(WifiLogBase):
id: int
start_time: datetime
end_time: datetime | None = None
class Config:
from_attributes = True
그 후 안드로이드에서 작업을 이어 나갔다
1. wifi가 등록이 되었을때 등록되어 있는 wifi와 현재 wifi가 일치할 경우 로깅 시작
2. wifi에 연결을 하였는데 해당 wifi가 등록이 되어 있는 경우 로깅 시작
3. wifi가 해제 되었을 시 로깅 종료
4. 등록된 wifi에 연결을 하다가 다른 wifi에 연결을 하였는데 등록이 되어 있지않은 경우 로깅 종료
이런식으로 조건을 만들 수 있을 것이다
그리고 현재의 backend 로직 상 wifi 등록 시에 해당 user_id가 response되어 나오고, log할때 이가 필요하므로 우선 data class를 만들었다
package com.example.app.data
data class UserResponse(
val user_name: String,
val home_ssid: String,
val home_bssid: String,
val id: Int // user_id
)
package com.example.app.data
data class StartLogRequest(
val user_id: Int
)
그 이후 api service 인터페이스도 추가하였다
interface ApiService {
@POST("user/create")
suspend fun registerUser(@Body request: UserCreateRequest): UserResponse
@POST("log/start")
suspend fun startLog(@Body request: StartLogRequest): LogResponse
@POST("log/end")
suspend fun endLog(@Query("log_id") logId: Int): LogResponse
}
그리고 view model에서는
fun registerWifiInfo(userName: String) {
val currentState = _uiState.value
if (userName.isBlank() || currentState.ssid.isBlank()) {
_uiState.update { it.copy(registrationStatus = "이름과 Wi-Fi 정보가 모두 필요합니다.") }
return
}
viewModelScope.launch {
try {
_uiState.update { it.copy(registrationStatus = "등록 중...") }
val request = UserCreateRequest(userName, currentState.ssid, currentState.bssid)
val res: UserResponse = RetrofitClient.instance.registerUser(request)
userId = res.id
homeSsid = res.home_ssid
homeBssid = res.home_bssid
// TODO: DataStore에 userId 저장
_uiState.update { it.copy(registrationStatus = "등록 성공 (user_id=${res.id})") }
// 등록 직후 현재 Wi‑Fi가 집과 일치하면 즉시 로그 시작
val now = _uiState.value
if (currentLogId == null && isHomeWifi(now.ssid, now.bssid)) {
startHomeLog()
}
} catch (e: Exception) {
_uiState.update { it.copy(registrationStatus = "오류 발생: ${e.message}") }
}
}
}
위 1번 사항을 위한 기능과
//wifi 연결 시 호출
fun startHomeLog() {
val uid = userId ?: return
if (currentLogId != null) return // 이미 시작됨
viewModelScope.launch {
try {
val log: LogResponse = RetrofitClient.instance.startLog(StartLogRequest(uid))
currentLogId = log.id
// TODO: DataStore에 currentLogId 저장
} catch (_: Exception) { /* no-op */ }
}
}
이후 현재 wifi와 등록된 wifi가 같다면 Log를 시작하며
//wifi 해제 시 호출
fun endHomeLog() {
val logId = currentLogId ?: return
viewModelScope.launch {
try {
RetrofitClient.instance.endLog(logId)
currentLogId = null
// TODO: DataStore에서 currentLogId 제거
} catch (_: Exception) { /* no-op */ }
}
}
// 네트워크 콜백에서 호출: 현재 wifi 가 집인지 확인 후 start
fun onWifiAvailable(context: Context) {
getWifiInfo(context)
val state = _uiState.value
if (isHomeWifi(state.ssid, state.bssid)) {
// 집 Wi‑Fi에 연결됨 → 시작 시도
startHomeLog()
} else {
// 집이 아닌 Wi‑Fi에 연결됨 → 진행 중이면 종료
if (currentLogId != null) endHomeLog()
}
}
private fun isHomeWifi(currentSsid: String, currentBssid: String): Boolean {
val hb = homeBssid
if (!hb.isNullOrBlank() && currentBssid.equals(hb, ignoreCase = true)) return true
val hs = homeSsid
return !hs.isNullOrBlank() && currentSsid == hs
}
// 네트워크 콜백에서 호출: Wi‑Fi 해제 시 종료 시도
fun onWifiLost() {
if (currentLogId != null) {
endHomeLog()
}
}
그리고 해제에 관련된 로직은 다음과 같다
이후로는 다음의 단계가 있을 것이다
1. datastore로 response data 저장 로직(user_id 혹은 log_id)
2. 네트워크가 없을 시 (offline) 네트워크가 연결 되었을때 즉시 log 종료 호출
3. 백그라운드 실행(로그)
4. 통계 분석 API 개발
5. 통계 시각화
6. 클라우드 배포
다음의 일정이 남아있다
'개발일지 > 안드로이드' 카테고리의 다른 글
| [안드로이드] Wifi기반 체류 시간 앱 - 6 (0) | 2025.08.23 |
|---|---|
| [안드로이드] Wifi기반 체류 시간 앱 - 5 (4) | 2025.08.18 |
| [안드로이드] Wifi기반 체류 시간 앱 - 3 (2) | 2025.08.15 |
| [안드로이드] Wifi기반 체류 시간 앱 - 2 (2) | 2025.08.13 |
| [안드로이드] Wifi기반 체류 시간 앱 - 1 (0) | 2025.08.11 |