본문 바로가기
조그만 기술로 세상을 이롭게/나여기있어

나 여기있어앱 개발기: QR 연결, GPS 위치, FCM PUSH, Flask 서버, MariaDB, C# 관리 프로그램까지

by eplus 2026. 5. 28.

1. 개발 배경

최근 1인 가구, 노인 돌봄, 아이 안전 확인, 가족 간 안부 확인과 같은 생활형 안전 서비스의 필요성이 커지고 있다.
단순히 “살아있다/괜찮다”를 확인하는 수준을 넘어, 상태와 위치를 함께 전달하고 보호자가 즉시 확인할 수 있는 구조가 필요하다.

이번에 개발한 안심체크는 다음 기능을 목표로 설계했다.

  • 회원가입 없이 기기 기반으로 사용
  • QR 코드로 보호 대상 폰과 보호자 폰 연결
  • 상태 선택: 좋음, 보통, 힘듦, 위급
  • GPS 위치와 전송일시 포함
  • Flask 서버를 통한 즉시 FCM PUSH 전송
  • 수신 폰에서 PUSH 수신 후 위치 지도 표시
  • 최근 수신 로그 30건 저장
  • C# WinForms + Syncfusion 기반 관리자 프로그램 제공
  • MariaDB 기반 운영 로그 관리

핵심은 “폰과 폰이 직접 통신하는 것처럼 보이지만, 실제로는 서버가 중간에서 상태 저장과 PUSH 발송을 안정적으로 처리하는 구조”다.


2. 전체 시스템 구조

안심체크는 크게 4개 영역으로 구성된다.

MAUI Android 앱
    ↓
Flask API 서버
    ↓
MariaDB
    ↓
Firebase Cloud Messaging
    ↓
상대방 Android 앱

운영 관리는 별도의 C# 관리 프로그램에서 처리한다.

C# WinForms 관리자 프로그램
    ↓
Flask 관리자 API
    ↓
MariaDB / FCM 발송 로그

전체 흐름은 다음과 같다.

1. 앱 최초 실행
2. 기기ID 생성
3. FCM 토큰 발급
4. 서버에 기기 등록
5. 내 QR 생성
6. 상대 폰에서 QR 스캔
7. 서버에 연결 정보 등록
8. 상태 전송
9. 서버가 DB 저장
10. 서버가 연결된 상대 폰으로 FCM PUSH 발송
11. 상대 폰은 알림 수신
12. 앱 화면에 상태, 위치, 전송일시 표시
13. 최근 로그 30건 저장 및 조회

3. 회원가입 없는 기기 기반 구조

일반적인 서비스는 회원가입, 로그인, 사용자 계정이 필요하다.
하지만 안심체크는 아이나 노인도 쉽게 사용할 수 있어야 하므로 회원가입을 제거했다.

대신 앱 최초 실행 시 고유한 device_id를 생성한다.

예:

device_id = 2e85ec1946bd465ebb5d451ef6963937

이 값은 앱 내부에 저장되며, 서버에서는 사용자를 식별하는 기본 키처럼 활용된다.

기기 등록 시 서버로 전송되는 기본 정보는 다음과 같다.

{
  "deviceId": "2e85ec1946bd465ebb5d451ef6963937",
  "nickname": "내 폰",
  "fcmToken": "Firebase에서 발급된 토큰",
  "platform": "Android",
  "appVersion": "1.0.0"
}

이 방식의 장점은 다음과 같다.

회원가입 없음
전화번호 입력 없음
아이디/비밀번호 없음
QR 스캔만으로 연결 가능
노인/아이도 쉽게 사용 가능

4. QR 코드 기반 폰 연결 방식

폰끼리 연결할 때 가장 고민한 부분은 “어떻게 쉽게 연결할 것인가”였다.

블루투스, NFC, Wi-Fi Direct 같은 방식도 가능하지만, 기종별 호환성이나 권한 문제가 많다.
그래서 최종적으로 QR 코드 연결 방식을 선택했다.

QR 생성 흐름

보호 대상 폰에서 “내 QR 생성”을 누르면 서버에 연결 코드를 요청한다.

POST /api/safecheck/pair/create

서버는 6자리 pairCode를 생성한다.

예:

421733

그리고 이 값을 QR 코드 데이터로 만든다.

{
  "type": "safecheck_pair",
  "pairCode": "421733"
}

QR 읽기 흐름

보호자 폰에서 “상대 QR 읽기”를 누르고 QR을 스캔하면 서버에 연결 요청을 보낸다.

POST /api/safecheck/pair/accept

서버는 다음을 확인한다.

pairCode 존재 여부
만료 여부
이미 사용 여부
동일 기기 여부
이미 연결된 기기 여부

정상 연결되면 sc_pair 테이블에 연결 관계가 저장된다.

owner_device_id  = QR을 만든 폰
target_device_id = QR을 읽은 폰
status           = ACTIVE

QR 재스캔 처리

같은 QR을 같은 폰이 다시 읽는 경우도 고려했다.
초기에는 이미 사용된 QR이라 오류가 발생했지만, 실제 사용에서는 사용자가 같은 QR을 여러 번 스캔할 수 있다.

따라서 이미 같은 두 기기가 연결되어 있다면 오류가 아니라 다음처럼 처리하는 것이 맞다.

{
  "result": "ok",
  "message": "already paired"
}

5. 상태 전송 기능

안심체크 앱의 핵심 화면은 상태 전송이다.

상태는 4가지로 구성했다.

좋음
보통
힘듦
위급

사용자가 상태를 선택하면 앱은 현재 GPS 위치를 가져오고, 전송일시와 함께 서버로 전송한다.

{
  "deviceId": "1fed7f587ec649678b0cfee0a772d344",
  "status": "GOOD",
  "statusText": "좋음",
  "message": "괜찮습니다.",
  "latitude": 35.178445,
  "longitude": 128.59846333333334,
  "sentAt": "2026-05-28T06:54:11"
}

서버 API는 다음과 같다.

POST /api/safecheck/status/send

서버는 수신 즉시 다음 순서로 처리한다.

1. 상태 로그 저장
2. 연결된 상대 기기 조회
3. 상대 기기의 FCM 토큰 조회
4. FCM PUSH 즉시 발송
5. 발송 결과 로그 저장
6. 앱에 결과 반환

6. Firebase Cloud Messaging PUSH 구조

PUSH 알림은 Firebase Cloud Messaging, 즉 FCM을 사용했다.

FCM을 사용하려면 두 가지 파일이 필요하다.

google-services.json
    → Android 앱용

firebase-service-account.json
    → Flask 서버용

Android 앱에서는 google-services.json을 통해 Firebase 프로젝트와 연결하고, 앱 실행 시 FCM 토큰을 발급받는다.

서버에서는 Firebase 서비스 계정 JSON을 이용해 FCM Admin SDK로 PUSH를 발송한다.

서버의 파일 위치는 웹에서 접근할 수 없는 곳에 두어야 한다.

권장:
C:\secure\firebase-service-account.json

비권장:
C:\inetpub\wwwroot\
static\
templates\

Flask 서버에서는 다음과 같이 서비스 계정 키를 읽는다.

FCM_CREDENTIALS = r"C:\secure\firebase-service-account.json"

또는 운영 환경에서는 환경변수로 관리할 수 있다.

setx SAFECHECK_FCM_CREDENTIALS "C:\secure\firebase-service-account.json" /M

7. 서버에서 즉시 PUSH를 보내는 이유

처음에는 C# 관리 프로그램에서 FCM 발송을 처리하는 구조도 검토했다.
하지만 안전 확인 앱에서는 즉시성이 중요하다.

따라서 일반 상태 전송은 반드시 서버가 즉시 처리하는 구조로 설계했다.

앱 상태 전송
↓
Flask 서버 수신
↓
MariaDB 저장
↓
FCM 즉시 발송
↓
상대 폰 알림 수신

C# 관리 프로그램은 실시간 발송 주체가 아니라 관리용으로 사용한다.

상태 로그 조회
FCM 발송 로그 조회
실패 건 재전송
수동 PUSH 발송
기기 관리
연결 관리

8. FCM 메시지 구성

FCM 메시지는 알림 영역에 표시되는 title, body와 앱 내부에서 처리하는 data를 함께 사용한다.

예:

{
  "title": "[안심확인] 내 폰",
  "body": "상태: 좋음 / 시간: 2026-05-28 07:16",
  "data": {
    "type": "safecheck_status",
    "statusLogId": "26",
    "senderDeviceId": "1fed7f587ec649678b0cfee0a772d344",
    "senderName": "내 폰",
    "statusCode": "GOOD",
    "statusText": "좋음",
    "message": "",
    "latitude": "35.178445",
    "longitude": "128.59846333333334",
    "sentAt": "2026-05-28T06:54:11"
  }
}

앱이 실행 중이면 data를 받아 즉시 화면을 갱신한다.
앱이 꺼져 있으면 시스템 알림으로 표시되고, 앱 실행 후 서버 로그 조회를 통해 최근 수신 내역을 다시 가져온다.


9. 앱 수신 처리와 최근 로그 30건

수신 폰에서는 PUSH를 받은 뒤 다음 처리를 한다.

1. 상태 데이터 파싱
2. 앱 내부 로컬 로그 저장
3. 최근 수신 위치 영역 갱신
4. 지도 표시 갱신
5. 수신 로그 목록에 추가

최근 로그는 30건 기준으로 관리한다.

최근 수신 로그 30건
상태
보낸 폰 이름
전송일시
위도
경도
메시지

로그 목록에서 항목을 선택하면 지도 영역에 위치가 표시된다.

지도 표시 시에는 단순히 상태와 시간만 표시하면 누가 보냈는지 알기 어렵기 때문에 다음처럼 표시하도록 개선했다.

좋음 (아버지폰)
2026-05-28 07:16
위도 35.178445 / 경도 128.598463

10. 지도 표시 방식

처음에는 기본 위치 표시 형태로 구현했지만, 실제 사용성을 높이기 위해 지도 표시 방식을 개선했다.

지도에는 다음 정보가 표시된다.

상태
보낸 폰 이름
전송일시
위도
경도
메시지

향후에는 VWorld, Kakao Map, OpenStreetMap 등의 지도 API와 연동할 수 있다.

기본 구조는 WebView 기반 지도로 구성했다.

MAUI 앱
↓
WebView
↓
HTML 지도
↓
좌표 마커 표시

이 방식은 MAUI 앱에서 지도 SDK 의존성을 줄이고, HTML/JavaScript 기반으로 지도 표현을 확장할 수 있다는 장점이 있다.


11. MariaDB 테이블 구조

안심체크 운영에 사용한 주요 테이블은 다음과 같다.

sc_device

기기 정보를 저장한다.

device_id
nickname
fcm_token
platform
app_version
created_at
last_seen_at
active_yn

역할:

기기 등록
FCM 토큰 관리
닉네임 관리
마지막 접속시간 관리

sc_pair_code

QR 연결 코드를 저장한다.

pair_code
owner_device_id
owner_nickname
owner_fcm_token
status
expires_at
created_at
accepted_at

상태값:

WAIT     : QR 생성 후 대기
USED     : 연결 완료
EXPIRED  : 만료

sc_pair

폰과 폰의 연결 관계를 저장한다.

owner_device_id
target_device_id
owner_nickname
target_nickname
owner_fcm_token
target_fcm_token
status
accepted_at
deleted_at

상태값:

ACTIVE  : 정상 연결
DELETED : 비활성 또는 삭제

sc_status_log

상태 전송 로그를 저장한다.

sender_device_id
status_code
status_text
message
latitude
longitude
sent_at
created_at

이 테이블은 실제 사용자가 “좋음/보통/힘듦/위급”을 누른 기록이다.


sc_fcm_send_log

FCM 발송 결과를 저장한다.

status_log_id
from_device_id
to_device_id
title
body
success_yn
error_message
created_at

성공 여부는 success_yn으로 관리한다.

Y : 성공
N : 실패

발송 실패 시 error_message를 통해 원인을 확인할 수 있다.


12. Flask API 구성

서버는 Python Flask로 구성했다.

기본 API는 다음과 같다.

GET  /api/safecheck/ping
POST /api/safecheck/device/register
POST /api/safecheck/pair/create
POST /api/safecheck/pair/accept
POST /api/safecheck/status/send
GET  /api/safecheck/log/list
GET  /api/safecheck/monitor/recent
POST /api/safecheck/monitor/send

관리자 API는 다음과 같다.

GET  /api/safecheck/admin/devices
GET  /api/safecheck/admin/pairs
GET  /api/safecheck/admin/status-logs
GET  /api/safecheck/admin/fcm-logs
POST /api/safecheck/admin/manual-send
POST /api/safecheck/admin/resend
POST /api/safecheck/admin/pair/deactivate
POST /api/safecheck/admin/device/deactivate

API 테스트는 curl로 진행했다.

예:

curl -X POST https://www.eiot.kr/api/safecheck/status/send ^
-H "Content-Type: application/json" ^
-d "{\"deviceId\":\"TEST001\",\"status\":\"GOOD\",\"statusText\":\"좋음\",\"message\":\"테스트\",\"latitude\":35.2271,\"longitude\":128.6811,\"sentAt\":\"2026-05-28T13:55:00\"}"

정상 응답:

{
  "result": "ok",
  "statusLogId": 1,
  "receiverCount": 1,
  "sendResults": []
}

13. C# WinForms + Syncfusion 관리자 프로그램

운영자는 모바일 앱만으로 전체 상태를 관리하기 어렵다.
따라서 별도의 Windows 관리 프로그램을 만들었다.

관리 프로그램은 C# WinForms와 Syncfusion SfDataGrid를 사용했다.

주요 기능은 다음과 같다.

기기 목록 조회
QR 연결 목록 조회
상태 로그 조회
FCM 발송 로그 조회
수동 PUSH 발송
실패 PUSH 재전송
연결 비활성 처리
기기 비활성 처리
자동 새로고침
상세 JSON 확인

관리 프로그램의 API 호출 구조는 다음과 같다.

C# WinForms
↓
SafeCheckApiClient
↓
Flask 관리자 API
↓
MariaDB / Firebase

수동 PUSH는 관리 프로그램이 Firebase에 직접 보내지 않고, 서버 API를 호출한다.

관리 프로그램
↓
/api/safecheck/admin/manual-send
↓
Flask 서버
↓
FCM 발송

이렇게 하면 Firebase 서비스 계정 키를 PC 프로그램에 넣지 않아도 된다.
보안상 매우 중요한 구조다.


14. 운영 중 발생한 주요 오류와 해결

1. deviceId and fcmToken required

앱에서 서버로 JSON을 보냈지만 Flask가 값을 읽지 못한 경우 발생했다.

해결:

Content-Type: application/json 지정
request.get_json(force=True) 적용
camelCase / PascalCase 모두 수용

2. Unknown column 오류

서버 코드와 DB 테이블 컬럼이 불일치할 때 발생했다.

예:

Unknown column 'app_version'
Unknown column 'message'
Unknown column 'latitude'
Unknown column 'from_device_id'
Unknown column 'success_yn'

해결:

서버 코드 기준으로 DB 스키마 재정리
sc_device
sc_pair_code
sc_pair
sc_status_log
sc_fcm_send_log 컬럼 통일

3. Firebase service account file not found

서버에서 Firebase 서비스 계정 JSON을 찾지 못한 경우다.

해결:

C:\secure\firebase-service-account.json 위치에 파일 배치
safecheck_config.py에서 절대경로 지정
IIS App Pool 읽기 권한 부여
IIS App Pool 재시작

4. Illegal mix of collations

MariaDB에서 테이블별 문자셋이 서로 달라 발생했다.

해결:

ALTER DATABASE safeCheck
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

관련 테이블도 동일 Collation으로 통일했다.

ALTER TABLE sc_device CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE sc_pair CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE sc_status_log CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE sc_fcm_send_log CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

5. PUSH는 왔지만 앱 화면에 표시되지 않음

FCM 시스템 알림은 도착했지만 앱 내부 로그에 저장되지 않는 문제가 있었다.

해결:

FCM data payload 추가
앱 실행 중 수신 이벤트 처리
로컬 로그 저장
서버 로그 30건 재조회 기능 추가
지도 자동 갱신

15. 보안 고려사항

안심체크는 위치정보와 보호자 연결 정보를 다루므로 보안이 중요하다.

운영 시 반드시 고려해야 할 사항은 다음과 같다.

Firebase 서비스 계정 JSON 외부 노출 금지
API Key 적용
HTTPS 사용
위치정보 수집 동의
보호자 연결 동의
QR 코드 만료시간 적용
FCM 토큰 DB 암호화 또는 접근 제한
관리자 프로그램 접근 제한
DB 백업 및 로그 보관 정책 수립

특히 Firebase 서비스 계정 JSON은 절대 앱이나 웹 폴더에 두면 안 된다.

나쁜 예:
앱 내부 포함
웹 static 폴더 저장
GitHub 업로드

좋은 예:
C:\secure\firebase-service-account.json
서버 환경변수 관리
IIS App Pool 읽기 권한만 부여

16. 실제 운영 흐름

실제 사용 흐름은 단순하다.

보호 대상 폰

앱 설치
내 이름 설정
내 QR 생성
상태 선택
좋음/보통/힘듦/위급 전송

보호자 폰

앱 설치
상대 QR 읽기
연결 완료
상대 상태 PUSH 수신
지도에서 위치 확인
최근 수신 로그 확인

관리자

관리 프로그램 실행
기기 목록 확인
연결 목록 확인
상태 로그 확인
FCM 발송 로그 확인
실패 건 재전송
필요 시 수동 PUSH 발송

17. 향후 개선 방향

현재 구조는 기본적인 안전 확인 서비스로 동작한다.
추가로 개선할 수 있는 방향은 다음과 같다.

위급 상태 반복 알림
보호자 다중 연결
상태 미전송 자동 감지
일정 시간 미체크 시 자동 알림
위치 이동 이력 지도 표시
카카오 알림톡/SMS 보조 발송
관리자 웹 대시보드
사용자별 알림 설정
배터리 부족 알림
낙상 감지 센서 연동

특히 위급 상태는 단순 PUSH 1회가 아니라 반복 알림, SMS, 전화 연결까지 확장할 수 있다.


18. 정리

안심체크는 단순한 상태 전송 앱이 아니라 다음 기술이 결합된 생활 안전 시스템이다.

.NET MAUI Android 앱
QR 코드 연결
GPS 위치 수집
Firebase Cloud Messaging
Python Flask API
MariaDB 로그 관리
C# WinForms 관리자 프로그램
Syncfusion SfDataGrid
IIS FastCGI 운영

서비스 구조상 중요한 점은 “PUSH 발송은 반드시 서버에서 처리한다”는 것이다.

앱이 직접 상대방에게 PUSH를 보내는 방식은 보안상 적절하지 않다.
서버가 기기 등록, 연결 관계, 상태 로그, 발송 로그를 관리하고, Firebase Admin SDK를 통해 FCM을 발송하는 구조가 안정적이다.

최종 구조는 다음과 같이 정리할 수 있다.

앱은 입력과 수신 담당
서버는 판단과 발송 담당
DB는 이력과 연결 관리 담당
관리 프로그램은 운영과 재처리 담당

이런 구조를 기반으로 하면 노인 돌봄, 아이 안심 확인, 1인 가구 안전 확인, 현장 근무자 위치 보고, 긴급 상태 공유 등 다양한 서비스로 확장할 수 있다.

반응형