Firebase 홈페이지 이동
프로젝트 단계와 앱 단계 build.gradle 수정
앱 닉네임과 SHA-1은 선택사항
SHA-1은 터미널에서 ./gradlew signingReport
입력 후 ctrl + Enter
로 간단하게 얻을 수 있다.
Storage 규칙을 수정하여 인증된 사용자만 Firebase 저장소 버킷에 파일을 업로드할 수 있도록 한다.
프로젝트 설정 -> 서비스 계정 -> 새 비공개 키 생성
pip install firebase-admin
Firebase Admin SDK 사용자 인증, 앱 초기화, storageBucket 주소 지정
import firebase_admin
from firebase_admin import credentials
from firebase_admin import storage
# Firebase Key Path
cred = credentials.Certificate("firebase-adminsdk.json")
# Firebase App init
firebase_admin.initialize_app(cred,{
'storageBucket': 'boar-detection.appspot.com'
})
버킷이 하나일 경우 버킷 주소는 (프로젝트 이름).appspot.com
226줄 이미지 저장하는 코드 아래에 작성
cv2.imwrite(save_path, im0)
# firebase upload
if os.path.isfile(save_path):
try:
bucket = storage.bucket()
blob = bucket.blob(f"images/{os.path.basename(save_path)}")
blob.upload_from_filename(save_path)
except Exception as e:
print(f"Error uploading file: {e}")
else:
print(f"No file found at {save_path}")
save_path에 파일이 존재하면 스토리지 버킷의 images 폴더 내에 파일의 기본 이름으로 업로드
파일 이름에 "/", "?", "#" 등의 기호를 사용하면 URL에 문제가 발생할 수 있어서 사용을 피하는 게 좋다
# Remove saved image
if os.path.isfile(save_path):
os.remove(save_path)
라즈베리파이에 파일이 쌓이지 않도록 파일 업로드 이후에 제거했다.
images 폴더에 이미지가 업로드되었고, 클릭하면 이미지를 확인할 수 있다.
FCM token을 라즈베리파이에서 가져올 수 있도록 Realtime Database를 사용한다.
implementation 'com.google.firebase:firebase-database'
Firebase Messaging 의존성 추가, [Sync Now] 클릭
implementation 'com.google.firebase:firebase-messaging'
기기의 FCM 토큰 확인하고 Realtime Database에 저장
// FCM 토큰 가져오기
FirebaseMessaging.getInstance().getToken()
.addOnCompleteListener(task -> {
if (!task.isSuccessful()) {
Log.w(TAG, "Fetching FCM registration token failed", task.getException());
return;
}
// 토큰 가져오기 성공
String token = task.getResult();
Log.d(TAG, "FCM registration token: " + token);
// Firebase Realtime Database에서 토큰 조회
DatabaseReference tokensRef = FirebaseDatabase.getInstance().getReference("tokens");
Query query = tokensRef.orderByValue().equalTo(token);
query.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
// 이미 저장된 토큰이 있을 경우 처리할 로직 추가
Log.d(TAG, "Token already exists");
} else {
// Firebase Realtime Database에 토큰 저장
tokensRef.push().setValue(token);
Log.d(TAG, "Token saved to database");
}
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
Log.e(TAG, "Database error: " + databaseError.getMessage());
}
});
});
}
토큰은 기기가 변경되거나 앱이 재설치 될 때마다 새로 발급된다.
AndroidManifest.xml 파일에 인터넷 권한 추가
<uses-permission android:name="android.permission.INTERNET" />
AndroidManifest.xml 파일에 FirebaseMessagingService를 상속받는 서비스 클래스 추가
<service
android:name=".MyFirebaseMessagingService"
android:exported="false"
android:directBootAware="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
MyFirebaseMessagingService 클래스에 알림 표시 기능 추가
Notification.Builder , NotificationCompat.Builder
public class MyFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onNewToken(String token) {
// FCM 토큰이 갱신되었을 때 필요한 작업 수행
Log.d("FCM Log", "Refreshed token: " + token);
}
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// FCM 메시지 title과 body를 수신, sendNotification 메서드에서 처리
if (remoteMessage.getNotification() != null) {
String title = remoteMessage.getNotification().getTitle();
String body = remoteMessage.getNotification().getBody();
sendNotification(title, body);
}
}
private void sendNotification(String title, String body) {
// 알림 채널
String channelId = "my_channel_id";
String channelName = "My Channel";
int importance = NotificationManager.IMPORTANCE_HIGH;
// 알림 클릭 시 열릴 액티비티
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
// 알림 소리
Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
// 알림 생성
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true) // 알림 클릭 시 사라지기
.setSound(soundUri)
.setContentIntent(pendingIntent)
// 알림 매니저를 통해 알림 표시
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.createNotificationChannel(new NotificationChannel(channelId, channelName, importance));
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED) {
notificationManager.notify(0, builder.build());
}
}
Android 13 이상에서 런타임 알림 권한 요청
알림을 받으려면 알림 권한을 허용해야 한다. UI로 알림을 사용했을 때의 기능을 설명하고 사용자가 확인하면 알림 권한을 요청. 허용 시 앱에서 알림을 표시할 수 있도록 한다.
TIRAMISU 이상에서 알림 권한을 요청하는 다이얼로그를 표시하고,
알림 권한 거부 시에 권한 설정을 요청하는 스낵바를 표시하도록 했다.
private static final int PERMISSION_REQUEST_NOTIFICATION = 1;
private void checkNotificationPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
// 권한이 없는 경우 권한 요청
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.POST_NOTIFICATIONS},
PERMISSION_REQUEST_NOTIFICATION);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_NOTIFICATION) {
if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
// 권한이 거부된 경우 스낵바 또는 다이얼로그 등으로 알림을 표시하여 사용자에게 권한 필요성 알리기
showPermissionSnackbar();
}
}
}
private void showPermissionSnackbar() {
Snackbar.make(findViewById(android.R.id.content), "알림 권한이 필요합니다.", Snackbar.LENGTH_LONG)
.setAction("권한 설정", new View.OnClickListener() {
@Override
public void onClick(View v) {
// 앱 설정 화면으로 이동하여 알림 권한 설정을 요청할 수 있음
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
}
})
.show();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 알림 권한 체크 및 요청
checkNotificationPermission();
...
}
알림 권한 요청을 구분하기 위한 식별자 상수 변수 PERMISSION_REQUEST_NOTIFICATION = 1;
권한을 체크하는 checkNotificationPermission() 메서드에서 ContextCompat.checkSelfPermission()
으로 PERMISSION_GRANTED를 확인하고 아닐 시에 SDK 33 TIRAMISU 이상이면 ActivityCompat.requestPermissions()
로 POST_NOTIFICATIONS
알림 권한을 요청하는 다이얼로그를 표시하고 사용자의 응답을 기다린다.
이후에 응답 결과가 onRequestPermissionsResult()
호출되어 requestCode에는 식별자가 grandResults에는 권한의 허용 여부 PackageManager.PERMISSION_GRANTED 또는 PackageManager.PERMISSION_DENIED가 전달된다.
grantResults 배열에서 PackageManager.PERMISSION_GRANTED가 아닐 시에 권한이 거부되었다고 판단하여 showPermissionSnackbar()를 호출하여 권한 설정을 요청하는 스낵바를 띄운다.
<!-- background에서 사용할 아이콘 설정 -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_notification" />
테스트 메시지 전송
버튼 클릭 -> 토큰 입력 후 +
버튼 클릭 -> 기기가 등록 된다.
detect.py 알림 생성 주기를 제어할 변수 previous_notification_time,
FCM 토큰 입력 변수 registration_token
import firebase_admin
from firebase_admin import credentials
from firebase_admin import storage
from firebase_admin import messaging
from firebase_admin import db
import time
previous_notification_time = 0
# Firebase Key Path
cred = credentials.Certificate("firebase-adminsdk.json")
# Firebase App init
firebase_admin.initialize_app(cred,{
'storageBucket': 'boar-detection.appspot.com',
'databaseURL': 'https://boar-detection-default-rtdb.firebaseio.com'
})
# Firebase Realtime Database에서 토큰 가져오기
ref = db.reference('tokens')
tokens = ref.get()
이미지 업로드 아래 메시지 전송 코드 추가
객체가 검출될 때마다 알림이 가면 너무 남발이기 때문에 10초마다 알림을 보내도록 한다.
# send message
current_time = time.time()
if current_time - previous_notification_time >= 10:
for token_key in tokens:
token = tokens[token_key]
message = messaging.Message(
notification=messaging.Notification(
title='멧돼지가 검출되었습니다!',
body='앱을 접속하여 확인하세요',
),
token=token,
)
response = messaging.send(message)
print('Successfully sent message:', response)
previous_notification_time = current_time
라즈베리파이4에서 카메라에 멧돼지가 검출되면 이미지를 저장하고 Storage에 업로드,
FCM으로 앱에 푸시 알림이 가도록 완료했다.
안드로이드 스튜디오 관련해서 처음 써봐서 그런데 혹시 위 코드가 어디에 추가되어야 하는지 알려주실수 있나요?