# 펌웨어 버전 앱 호환성 관리

**Last Updated**: 2026-04-01

## 1. 개요

이 문서는 Roomfit 애플리케이션의 펌웨어 업데이트 시스템에서 앱 버전과 펌웨어 버전 간의 호환성을 관리하는 방법을 설명합니다. 다양한 앱 버전에 따라 펌웨어 업데이트의 필수 여부 및 설치 가능 여부를 제어하는 기능을 제공합니다.

> Note:
> 현재 repo에는 이 문서가 가정하는 `firmwareRepository`, `firmwareUpdateService`, `firmware_versions` 테이블 기반 구현이 포함되어 있지 않습니다.
> 따라서 이 문서는 **현재 구현 문서라기보다 설계/reference 문서**로 취급해야 합니다.

## 2. 데이터베이스 구조 확장

### 2.1 firmware_versions 테이블 필드 추가

기존 `firmware_versions` 테이블에 앱 버전 호환성 관리를 위한 필드를 추가했습니다.

```sql
ALTER TABLE firmware_versions
ADD COLUMN min_required_app_version VARCHAR DEFAULT NULL,
ADD COLUMN max_required_app_version VARCHAR DEFAULT NULL,
ADD COLUMN min_compatible_app_version VARCHAR DEFAULT NULL,
ADD COLUMN max_compatible_app_version VARCHAR DEFAULT NULL;

COMMENT ON COLUMN firmware_versions.min_required_app_version IS '이 펌웨어 버전이 필수인 최소 앱 버전 (이상일 경우 필수 설치)';
COMMENT ON COLUMN firmware_versions.max_required_app_version IS '이 펌웨어 버전이 필수인 최대 앱 버전 (이하일 경우 필수 설치)';
COMMENT ON COLUMN firmware_versions.min_compatible_app_version IS '이 펌웨어를 설치할 수 있는 최소 앱 버전 (이상일 경우 설치 가능)';
COMMENT ON COLUMN firmware_versions.max_compatible_app_version IS '이 펌웨어를 설치할 수 있는 최대 앱 버전 (이하일 경우 설치 가능)';
```

## 3. 지원 시나리오

새로 추가된 필드들로 다음과 같은 시나리오를 구현할 수 있습니다:

### 3.1 어떤 앱 버전 이상일 경우 필수 설치

특정 앱 버전 이상을 사용하는 사용자에게만 펌웨어 업데이트를 필수로 설정하려면 `min_required_app_version` 필드를 사용합니다.

**예시:**
- `min_required_app_version = '2.0.0'`으로 설정한 경우, 앱 버전이 2.0.0 이상인 사용자에게는 이 펌웨어 업데이트가 필수가 됩니다.
- 앱 버전이 2.0.0 미만인 사용자에게는 필수가 아닙니다.

**데이터 설정 예시:**
```sql
UPDATE firmware_versions 
SET min_required_app_version = '2.0.0', min_compatible_app_version = '2.0.0'
WHERE id = 'firmware_id';
```

### 3.2 어떤 앱 버전 이상 설치 가능

특정 앱 버전 이상을 사용하는 사용자만 펌웨어를 설치할 수 있도록 하려면 `min_compatible_app_version` 필드를 사용합니다.

**예시:**
- `min_compatible_app_version = '1.5.0'`으로 설정한 경우, 앱 버전이 1.5.0 이상인 사용자만 이 펌웨어를 설치할 수 있습니다.
- 앱 버전이 1.5.0 미만인 사용자에게는 이 펌웨어 업데이트가 표시되지 않거나 설치할 수 없습니다.

**데이터 설정 예시:**
```sql
UPDATE firmware_versions 
SET min_compatible_app_version = '1.5.0'
WHERE id = 'firmware_id';
```

### 3.3 어떤 앱 버전 이하일 경우 필수 설치

특정 앱 버전 이하를 사용하는 사용자에게만 펌웨어 업데이트를 필수로 설정하려면 `max_required_app_version` 필드를 사용합니다.

**예시:**
- `max_required_app_version = '2.5.0'`으로 설정한 경우, 앱 버전이 2.5.0 이하인 사용자에게는 이 펌웨어 업데이트가 필수가 됩니다.
- 앱 버전이 2.5.0 초과인 사용자에게는 필수가 아닙니다.

**데이터 설정 예시:**
```sql
UPDATE firmware_versions 
SET max_required_app_version = '2.5.0', min_compatible_app_version = '1.0.0'
WHERE id = 'firmware_id';
```

### 3.4 어떤 앱 버전 이하일 경우 설치 가능

특정 앱 버전 이하를 사용하는 사용자만 펌웨어를 설치할 수 있도록 하려면 `max_compatible_app_version` 필드를 사용합니다.

**예시:**
- `max_compatible_app_version = '3.0.0'`으로 설정한 경우, 앱 버전이 3.0.0 이하인 사용자만 이 펌웨어를 설치할 수 있습니다.
- 앱 버전이 3.0.0 초과인 사용자에게는 이 펌웨어 업데이트가 표시되지 않거나 설치할 수 없습니다.

**데이터 설정 예시:**
```sql
UPDATE firmware_versions 
SET max_compatible_app_version = '3.0.0', min_compatible_app_version = '1.0.0'
WHERE id = 'firmware_id';
```

### 3.5 앱 버전에 관계 없이 업데이트 가능

모든 앱 버전에서 펌웨어 업데이트를 설치할 수 있도록 하려면 호환성 필드를 모두 NULL로 설정하거나, 매우 넓은 범위로 설정합니다.

**예시:**
- 모든 호환성 필드를 NULL로 설정: 제한 없음
- 또는 `min_compatible_app_version = '0.0.1'`, `max_compatible_app_version = NULL`과 같이 매우 낮은 최소 버전으로 설정

**데이터 설정 예시:**
```sql
UPDATE firmware_versions 
SET min_required_app_version = NULL,
    max_required_app_version = NULL,
    min_compatible_app_version = NULL,
    max_compatible_app_version = NULL
WHERE id = 'firmware_id';
```

## 4. 앱에서 활용 방법

### 4.1 펌웨어 업데이트 가능 여부 확인

앱에서 현재 앱 버전을 기준으로 설치 가능한 펌웨어를 확인하는 쿼리:

```sql
SELECT * FROM firmware_versions
WHERE is_active = true
AND (min_compatible_app_version IS NULL OR min_compatible_app_version <= '현재_앱_버전')
AND (max_compatible_app_version IS NULL OR max_compatible_app_version >= '현재_앱_버전')
ORDER BY released_at DESC
LIMIT 1;
```

### 4.2 필수 펌웨어 업데이트 확인

앱에서 현재 앱 버전을 기준으로 필수 업데이트가 필요한 펌웨어를 확인하는 쿼리:

```sql
SELECT * FROM firmware_versions
WHERE is_active = true
AND is_required = true
AND (
  (min_required_app_version IS NOT NULL AND min_required_app_version <= '현재_앱_버전')
  OR
  (max_required_app_version IS NOT NULL AND max_required_app_version >= '현재_앱_버전')
  OR
  (min_required_app_version IS NULL AND max_required_app_version IS NULL)
)
ORDER BY released_at DESC
LIMIT 1;
```

## 5. 모델 확장

### 5.1 FirmwareVersion 모델 확장

기존 FirmwareVersion 모델에 새로운 필드를 추가합니다:

```dart
class FirmwareVersion {
  final String id;
  final String version;
  final String fileUrl;
  final int fileSize;
  final String? releaseNotes;
  final bool isRequired;
  final bool isActive;
  final DateTime createdAt;
  final DateTime releasedAt;
  
  // 새로 추가된 필드
  final String? minRequiredAppVersion;
  final String? maxRequiredAppVersion;
  final String? minCompatibleAppVersion;
  final String? maxCompatibleAppVersion;
  
  // 생성자
  FirmwareVersion({
    required this.id,
    required this.version,
    required this.fileUrl,
    required this.fileSize,
    this.releaseNotes,
    required this.isRequired,
    required this.isActive,
    required this.createdAt,
    required this.releasedAt,
    this.minRequiredAppVersion,
    this.maxRequiredAppVersion,
    this.minCompatibleAppVersion,
    this.maxCompatibleAppVersion,
  });
  
  // fromJson 메서드 확장
  factory FirmwareVersion.fromJson(Map<String, dynamic> json) {
    return FirmwareVersion(
      id: json['id'],
      version: json['version'],
      fileUrl: json['file_url'],
      fileSize: json['file_size'],
      releaseNotes: json['release_notes'],
      isRequired: json['is_required'],
      isActive: json['is_active'],
      createdAt: DateTime.parse(json['created_at']),
      releasedAt: DateTime.parse(json['released_at']),
      minRequiredAppVersion: json['min_required_app_version'],
      maxRequiredAppVersion: json['max_required_app_version'],
      minCompatibleAppVersion: json['min_compatible_app_version'],
      maxCompatibleAppVersion: json['max_compatible_app_version'],
    );
  }
  
  // toJson 메서드 확장
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'version': version,
      'file_url': fileUrl,
      'file_size': fileSize,
      'release_notes': releaseNotes,
      'is_required': isRequired,
      'is_active': isActive,
      'created_at': createdAt.toIso8601String(),
      'released_at': releasedAt.toIso8601String(),
      'min_required_app_version': minRequiredAppVersion,
      'max_required_app_version': maxRequiredAppVersion,
      'min_compatible_app_version': minCompatibleAppVersion,
      'max_compatible_app_version': maxCompatibleAppVersion,
    };
  }
  
  // 앱 버전 호환성 확인 메서드
  bool isCompatibleWithAppVersion(String appVersion) {
    if (minCompatibleAppVersion == null && maxCompatibleAppVersion == null) {
      return true; // 제한 없음
    }
    
    final List<int> appParts = _parseVersion(appVersion);
    
    if (minCompatibleAppVersion != null) {
      final List<int> minParts = _parseVersion(minCompatibleAppVersion!);
      if (_compareVersions(appParts, minParts) < 0) {
        return false; // 앱 버전이 최소 요구 버전보다 낮음
      }
    }
    
    if (maxCompatibleAppVersion != null) {
      final List<int> maxParts = _parseVersion(maxCompatibleAppVersion!);
      if (_compareVersions(appParts, maxParts) > 0) {
        return false; // 앱 버전이 최대 호환 버전보다 높음
      }
    }
    
    return true;
  }
  
  // 앱 버전에 대한 필수 업데이트 여부 확인 메서드
  bool isRequiredForAppVersion(String appVersion) {
    if (!isRequired) return false;
    
    if (minRequiredAppVersion == null && maxRequiredAppVersion == null) {
      return true; // 모든 버전에 필수
    }
    
    final List<int> appParts = _parseVersion(appVersion);
    
    if (minRequiredAppVersion != null) {
      final List<int> minParts = _parseVersion(minRequiredAppVersion!);
      if (_compareVersions(appParts, minParts) >= 0) {
        return true; // 앱 버전이 최소 필수 버전 이상
      }
    }
    
    if (maxRequiredAppVersion != null) {
      final List<int> maxParts = _parseVersion(maxRequiredAppVersion!);
      if (_compareVersions(appParts, maxParts) <= 0) {
        return true; // 앱 버전이 최대 필수 버전 이하
      }
    }
    
    return false;
  }
  
  // 버전 문자열을 정수 배열로 파싱하는 내부 메서드
  List<int> _parseVersion(String version) {
    return version.split('.').map((part) => int.parse(part)).toList();
  }
  
  // 버전 비교를 위한 내부 메서드
  int _compareVersions(List<int> v1, List<int> v2) {
    for (int i = 0; i < math.min(v1.length, v2.length); i++) {
      if (v1[i] > v2[i]) return 1;
      if (v1[i] < v2[i]) return -1;
    }
    
    if (v1.length > v2.length) return 1;
    if (v1.length < v2.length) return -1;
    
    return 0; // 동일한 버전
  }
}
```

## 6. 서비스 확장

### 6.1 FirmwareUpdateService 확장

펌웨어 업데이트 서비스에 앱 버전 호환성 체크 기능을 추가합니다:

```dart
class FirmwareUpdateService {
  // ... 기존 코드 ...
  
  // 현재 앱 버전에 맞는 펌웨어 업데이트 확인
  Future<FirmwareVersion?> getCompatibleFirmwareUpdate(
    String currentFirmwareVersion,
    String appVersion
  ) async {
    // 최신 펌웨어 버전 모두 조회
    final latestFirmwares = await firmwareRepository.getActiveFirmwareVersions();
    
    // 앱 버전과 호환되는 펌웨어만 필터링
    final compatibleFirmwares = latestFirmwares
        .where((firmware) => firmware.isCompatibleWithAppVersion(appVersion))
        .toList();
    
    if (compatibleFirmwares.isEmpty) return null;
    
    // 날짜순으로 최신 펌웨어 선택
    compatibleFirmwares.sort((a, b) => b.releasedAt.compareTo(a.releasedAt));
    
    // 현재 펌웨어보다 새로운 버전만 반환
    for (final firmware in compatibleFirmwares) {
      if (firmware.isNewerThan(currentFirmwareVersion)) {
        return firmware;
      }
    }
    
    return null;
  }
  
  // 앱 버전에 대해 필수 업데이트인지 확인
  bool isUpdateRequiredForApp(FirmwareVersion firmware, String appVersion) {
    return firmware.isRequiredForAppVersion(appVersion);
  }
}
```

## 7. 예시 사용 사례

### 7.1 앱에서 펌웨어 업데이트 체크 로직

```dart
Future<void> checkFirmwareUpdate() async {
  // 현재 앱 버전 및 기기 펌웨어 버전 가져오기
  final String appVersion = await PackageInfo.fromPlatform()
      .then((info) => info.version);
  final String currentFirmwareVersion = await deviceService.getCurrentFirmwareVersion();
  
  // 현재 앱 버전과 호환되는 펌웨어 업데이트 확인
  final availableUpdate = await firmwareUpdateService
      .getCompatibleFirmwareUpdate(currentFirmwareVersion, appVersion);
  
  if (availableUpdate == null) {
    // 업데이트 없음 또는 호환되는 업데이트 없음
    return;
  }
  
  // 필수 업데이트 여부 확인
  final isRequired = firmwareUpdateService
      .isUpdateRequiredForApp(availableUpdate, appVersion);
  
  if (isRequired) {
    // 필수 업데이트인 경우 강제 업데이트 다이얼로그 표시
    showMandatoryUpdateDialog(availableUpdate);
  } else {
    // 선택적 업데이트인 경우 알림 표시
    showOptionalUpdateNotification(availableUpdate);
  }
}
```

### 7.2 관리자 페이지에서 펌웨어 업데이트 등록

관리자 페이지에서 새 펌웨어를 등록할 때 앱 버전 호환성 정보를 함께.

```dart
Future<void> registerNewFirmware(FirmwareVersionForm form) async {
  final newFirmware = FirmwareVersion(
    id: const Uuid().v4(),
    version: form.version,
    fileUrl: form.fileUrl,
    fileSize: form.fileSize,
    releaseNotes: form.releaseNotes,
    isRequired: form.isRequired,
    isActive: true,
    createdAt: DateTime.now(),
    releasedAt: DateTime.now(),
    // 앱 버전 호환성 정보
    minRequiredAppVersion: form.isRequired ? form.minRequiredAppVersion : null,
    maxRequiredAppVersion: form.isRequired ? form.maxRequiredAppVersion : null,
    minCompatibleAppVersion: form.minCompatibleAppVersion,
    maxCompatibleAppVersion: form.maxCompatibleAppVersion,
  );
  
  await firmwareRepository.createFirmwareVersion(newFirmware);
}
```

## 8. 마이그레이션 계획

기존 펌웨어 데이터를 위한 업데이트 계획:

1. 스키마 업데이트: 데이터베이스에 새 열 추가
2. 기존 펌웨어 업데이트: 모든 활성 펌웨어에 기본 호환성 정보 적용
   ```sql
   -- 모든 기존 펌웨어를 앱 버전 1.0.0 이상에서 사용 가능하도록 설정
   UPDATE firmware_versions
   SET min_compatible_app_version = '1.0.0'
   WHERE min_compatible_app_version IS NULL;
   ```
3. 앱 업데이트: 클라이언트 앱이 새로운 호환성 정보를 처리할 수 있도록 업데이트

## 9. 결론

이 앱 버전 호환성 관리 시스템을 통해 다음과 같은 이점을 얻을 수 있습니다:

1. **세밀한 업데이트 정책**: 앱 버전에 따라 펌웨어 업데이트의 가용성과 필수 여부를 정밀하게 제어할 수 있습니다.
2. **유연한 업데이트 경로**: 특정 앱 버전에 대해서만 필수 업데이트를 적용하거나, 특정 버전 범위에서만 업데이트를 제공할 수 있습니다.
3. **호환성 문제 방지**: 특정 앱 버전과 펌웨어 간의 알려진 호환성 문제가 있는 경우, 해당 조합을 방지할 수 있습니다.
4. **점진적 롤아웃**: 새 펌웨어 기능을 특정 앱 버전 이상에서만 사용 가능하도록 하여 점진적인 기능 롤아웃이 가능합니다.

이 시스템은 기존 펌웨어 업데이트 관리 시스템의 자연스러운 확장으로, Roomfit 앱과 기기 간의 호환성을 보다 효과적으로 관리할 수 있게 해줍니다.
