반응형

앱스토어 배포를 위해 리뷰 전 'Validate App' 하면 아래 그림과 같이 NDEF 에러가 발생하는 경우가 있다. 이 해결방법을 알아보도록 하겠다.

 

1. Info.plist에 NFC관련 권한이 설정되어있는지 확인하자. 

<key>NFCReaderUsageDescription</key>
<string>This app would like to use NFC for some reason.</string>

 

2. 해당  앱의 App ID 설정에 'NFC Tage Reading'이 체크되어있는지 확인한다.

 

3. .entitlements 파일에 TAG설정이 되어있는지 확인하고 NDEF 부분은 제거한다.

<?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  <plist version="1.0">
  <dict>
      <key>com.apple.developer.nfc.readersession.formats</key>
      <array>
          <string>TAG</string>
          <!-- 'NDEF는 제거하도록 한다. (대부분 이부분 때문에 에러 발생됨)
          <string>NDEF</string>
          -->
      </array>
  </dict>
  </plist>

 

4. Clean & ReBuild

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
블로그 이미지

SKY STORY

,
반응형

안드로이드에서 targetSdkVersion 34로 설정할 때는 몇 가지 주요 변경 사항과 주의 사항이 있습니다. 주요 사항은 다음과 같다.

 

1. 권한 처리 강화

  • 미디어 파일 권한: Android 14에서는 사진, 동영상, 오디오 파일에 대한 권한 요청이 분리되었습니다. READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO와 같은 구체적인 권한을 사용해야 합니다.
  • 알림 권한: Android 13(API 33)부터 알림 사용을 위한 권한(POST_NOTIFICATIONS)이 필수적입니다. 사용자가 명시적으로 알림을 허용해야 앱이 알림을 보낼 수 있습니다.
    • 예시:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 100)
}

 

2. 백그라운드 작업 제한 강화

  • 정책 변경: Android 14는 백그라운드 작업에 대한 제약을 더 강화했습니다. 배터리 소모를 줄이기 위해 백그라운드에서 실행되는 작업에 대한 제한이 있으며, 특히 앱이 백그라운드에서 실행 중일 때 작업 스케줄링이 더 어려워졌습니다. 이를 해결하기 위해 JobScheduler 또는 WorkManager를 사용하는 것이 권장됩니다.

 

3. 기본 보안 정책 강화

  • SAF(Storage Access Framework) 적용 강화: 외부 저장소 접근이 더 제한적이며, 앱이 명시적으로 저장소 접근을 요청하지 않으면 사용자가 이를 거부할 수 있습니다. Scoped Storage 적용을 준비해야 하며, 파일 관리는 앱 전용 저장소나 사용자 상호작용을 통해서만 접근해야 합니다.

 

4. 암호화 및 보안

  • 암호화된 네트워크 통신: 명시적 암호화가 더 중요해졌습니다. Android 14는 더 엄격한 네트워크 보안 구성을 요구하며, 특히 비암호화된 HTTP 요청을 차단하거나 이를 명시적으로 허용하지 않으면 기본적으로 차단됩니다. 앱의 network_security_config.xml에서 설정을 확인하고 HTTPS를 권장합니다.

 

5. 개발자 옵션 사용 제한

  • Android 14에서는 디버그 앱이 아닌 경우, 앱 설치 후 개발자 옵션을 통해 설정된 일부 기능이 자동으로 차단될 수 있습니다. 따라서 개발자 전용 빌드 배포 빌드에서 설정 차이를 명확히 하고, 배포 빌드에서는 사용자 경험에 영향을 주지 않도록 주의해야 합니다.

 

6. 백그라운드 위치 접근 제한

  • 위치 서비스 관련 변경 사항으로, 백그라운드에서 위치 정보를 사용하는 경우에 대해 더 엄격한 권한 요구가 추가되었습니다. Android 10(API 29)부터 백그라운드 위치 권한(ACCESS_BACKGROUND_LOCATION)이 따로 분리되었으며, 이를 명시적으로 요청해야 합니다. 이제 위치 권한을 필요로 하는 경우, Foreground 권한 Background 권한을 각각 별도로 요청해야 합니다.

 

7. 동적 코드 로딩 제한

  • Android 14에서는 동적 코드 로딩(dynamic code loading)과 관련된 보안 정책이 더 엄격해졌습니다. APK 내에서 동적으로 코드를 로드하는 방식(예: 외부에서 다운받은 Dex 파일 로딩)을 사용한다면, 보안상의 이유로 Google Play 정책 위반이 될 수 있으므로 주의해야 합니다.

 

8. Non-SDK 인터페이스 사용 제한

  • Android 14에서는 Non-SDK 인터페이스(비공개 API) 사용을 제한하는 정책이 강화되었습니다. 이제 비공개 API에 접근하는 것은 더욱 어렵고, 이에 대한 경고나 오류가 발생할 수 있으므로, 공식 SDK를 사용하는지 점검해야 합니다.

 

 

반응형
블로그 이미지

SKY STORY

,
반응형

Gradle 8.0 이상에서는 이전 버전과 비교해 여러 사항들이 변경되었으며, 특히 안드로이드 빌드 설정에 직접적인 영향을 미치는 요소들이 많다. 몇 가지 주요 변경 사항은 다음과 같다.

 

1. namespace 선언 필수

  • 변경 사항: Gradle 8.0과 Android Gradle Plugin(AGP) 7.3.0 이상에서는 AndroidManifest.xml에서 패키지 이름을 설정하는 대신 build.gradle 파일에서 namespace 속성을 명시적으로 선언해야 합니다.
  • 이유: 이 변경 사항은 Android Gradle Plugin의 개선된 네임스페이스 지원에 따른 것으로, 더 나은 빌드 속도와 코드 분리 지원을 위해 도입되었습니다.
  • 예시:
android {
    namespace 'com.sample.test'
}

 

2. targetSdkVersion이 없어짐

  • 변경 사항: targetSdkVersion은 더 이상 Gradle 파일에서 필수적으로 선언되지 않으며, Google Play가 자동으로 최신 버전으로 간주합니다. 하지만 여전히 하위 호환성을 유지하고 싶다면 명시적으로 설정하는 것이 좋습니다.
  • 이유: 이는 구글이 최신 플랫폼 기능을 활용하도록 유도하기 위한 변화이며, 최신 SDK 버전에 대한 자동 지원을 강화합니다.
  • 예시:
android {
    compileSdkVersion 34
    defaultConfig {
        minSdkVersion 21
        // targetSdkVersion 없어짐
    }
}

 

3. 종속성 관리 변화

  • 변경 사항: Gradle 8.0에서는 jcenter() 지원이 완전히 중단되었습니다. 따라서 mavenCentral() 또는 google()로 대체해야 합니다.
  • 이유: JFrog에서 JCenter 저장소를 더 이상 업데이트하지 않기 때문에 더 이상 사용할 수 없습니다​.
  • 예시:
repositories {
    google()
    mavenCentral()
}

 

4. Java Toolchain 강화

  • 변경 사항: Gradle 8.0에서는 Java toolchain을 보다 엄격하게 관리하며, 잘못된 toolchain 설정이 더 이상 허용되지 않습니다. 또한 자동으로 toolchain을 다운로드할 때 명시적으로 저장소(Repository)를 설정하지 않으면 오류가 발생합니다.
  • 이유: 이는 빌드 환경에서 더 나은 일관성과 신뢰성을 제공하기 위해 도입되었습니다​.
  • 예시:
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
    }
}

 

5. 테스트 프레임워크 설정 변경

  • 변경 사항: 테스트 태스크에서 테스트 프레임워크를 설정한 후 추가적인 옵션을 변경하는 것이 더 이상 허용되지 않습니다. 테스트 프레임워크는 먼저 설정한 후 옵션을 설정해야 합니다.
  • 이유: 테스트 프레임워크 설정 후 옵션을 변경하는 것이 이전에는 설정을 덮어쓰는 문제가 있었기 때문에 이러한 변경이 적용되었습니다​.
  • 예시:
test {
    useJUnitPlatform {
        includeTags("unit")
    }
    options {
        // 테스트 옵션
    }
}

 

반응형
블로그 이미지

SKY STORY

,
반응형

성공과 실패

by netcanis

 

"성공 또는 실패의 반대말은 아무것도 하지 않는 것이다."

 

반응형
블로그 이미지

SKY STORY

,
반응형

AAR파일을 프로젝트에 추가 및 설정하는 방법에 대해 알아보도록 하겠습니다.

 

AAR 파일을 프로젝트에 추가

aar파일을 프로젝트의 libs 폴더에 복사합니다. 만약 폴더가 없다면 생성합니다.

your_project/
├── app/
│   ├── build.gradle.kts
│   ├── libs/
│   │   ├── MyTest-debug.aar
│   │   └── MyTest-release.aar
│   ├── src/
│   └── ...
├── build.gradle.kts
└── settings.gradle.kts

'

 

'settings.gradle.kts' 파일 수정

'settings.gradle.kts' 파일에서 'flatDir' repositories를 추가합니다:

pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        // 'libs' 폴더 설정  
        flatDir {
            dirs("libs")
        }
    }
}

rootProject.name = "your_project"
include(":app")

 

'build.gradle.kts' (Moduel :app) 파일 수정

... 생략 ...

dependencies {
    ... 생략 ...

    // AAR 파일 의존성 추가
    implementation(":MyTest-debug")
    releaseImplementation(":MyTest-release")
}

 

 

반응형
블로그 이미지

SKY STORY

,
반응형

개발중인 어플리케이션 프로젝트(.apk)를 라이브러리 모듈(.aar)로 변경해야 할 경우 설정방법을 알아보도록 하자.

프로젝트 이름은 'mytest'일 경우 다음과 같은 순서로 설정을 변경하도록 하자.

 

불필요한 소스 파일 제거

라이브러리 파일에 필요한 소스코드를 제외하고 나머지  파일들은 모두 제거하도록 한다.

 

AndroidManifest.xml 수정

application 요소와 앱 관련 설정을 제거하고, 라이브러리에서 필요한 설정만 남겨둡니다.

예를 들어, 권한이나 기본 설정 등을 유지할 수 있습니다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mytest">

    <!-- 필요한 권한 및 기타 설정을 여기에 추가 -->
    <!-- 예: INTERNET 권한 -->
    <uses-permission android:name="android.permission.INTERNET" />

    <!-- 필요하다면 다른 권한도 추가 가능 -->
    
    <!-- 라이브러리에서 제공하는 특정 기능을 위한 설정 -->
    <!-- 예: 서비스나 리시버 등 -->
    <!--
    <application>
        <service android:name=".MyService" />
        <receiver android:name=".MyReceiver">
            <intent-filter>
                <action android:name="com.example.mytest.SOME_ACTION" />
            </intent-filter>
        </receiver>
    </application>
    -->
</manifest>

 

'build.gradle.kts' (Project: MyTest)

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    alias(libs.plugins.android.application) apply false

    // 라이브러리 플러그인 추가
    alias(libs.plugins.android.library) apply false

    alias(libs.plugins.jetbrains.kotlin.android) apply false
}

 

'build.gradle.kts' (Module: MyTest)

plugins {
    // 어플리케이션 플러그인 제거
    //alias(libs.plugins.android.application)

	// 라이브러리 플러그인 추가
    alias(libs.plugins.android.library)

    alias(libs.plugins.jetbrains.kotlin.android)
}

android {
    namespace = "com.sample.mytest"
    compileSdk = 34

    defaultConfig {
        // applicationId 제거
        //applicationId = "com.sample.mytest"

        minSdk = 28

        // 버전정보 제거
        //versionCode = 1
        //versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
	
	... 생략 ...

    lint {
        targetSdk = 34
    }
	
	... 생략 ...
    
}

dependencies {
	... 생략 ...
}

 

'settings.gradle.kts' (Project Settings)

... 생략 ...

rootProject.name = "MyTest"	// 프로젝트 루트 이름
include(":MyTest")			// 모듈이름 정의

... 생략 ...

 

'libs.versions.toml' (Version Catalog)

[versions]
agp = "8.4.1"
kotlin = "1.9.0"
coreKtx = "1.13.1"
... 생략 ...

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
... 생략 ...

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
# 라이브러리 플러그인 버전정보 추가
android-library = { id = "com.android.library", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

 

라이브러리 빌드 및 테스트

콘솔창에서 아래와 같이 아리브러리 빌드를 한다. 

./gradlew assembleRelease

 

 

반응형

'개발 > Android' 카테고리의 다른 글

Gradle 8.0 이상 주요 변경사항  (1) 2024.10.04
AAR 라이브러리 사용  (0) 2024.06.05
OpenCV 설정  (0) 2024.05.30
aab파일 apk파일로 변환  (0) 2020.08.21
안드로이드 원격 디버깅 방법  (0) 2020.07.19
블로그 이미지

SKY STORY

,

OpenCV 설정

개발/Android 2024. 5. 30. 13:34
반응형

OpenCV Android 프로젝트에 포함하려면 OpenCV Android SDK 직접 다운로드하고 프로젝트에 포함해야 합니다. 다음은 OpenCV SDK 다운로드하고 프로젝트에 추가하는 방법입니다.

 

OpenCV SDK 다운로드 및 설정

OpenCV 공식 웹사이트에서 OpenCV Android SDK 다운로드합니다.
https://github.com/opencv/opencv/releases

 

Android Studio의 프로젝트에 OpenCV 모듈 추가합니다.

File/New/import Module... 선택합니다. 

 

sdk 폴더를 선택합니다.

 

Module name은 다음과 같이 설정합니다.

 

프로젝트 루트에 sdk폴더를 만들고 타깃 디렉토리로 선택합니다.

 

OpenCV gradle sdk 설정을 MyProject gradle sdk 설정과 동일하게 맞추어 줍니다.

OpenCV 4.9.0버전의 경우 아래와 같이 주석처리하고 MyProject 의 sdk설정으로 맞추어 주었습니다.

 

settings gradle 설정에 아래와 같이 opencv 가 포함되었는지 확인합니다.

 

의존성을 추가합니다.

 

빌드 에러가 발생되지 않으면 성공입니다. 

 

 

반응형
블로그 이미지

SKY STORY

,
반응형

UITextField, UITextView 입력 시 키보드가 활성화될 때 키보드에 가려지지 않도록 하는 방법을 알아보도록 한다.

아래 샘플은 UITableView에서 입력 시 키보드에 가려지지 않도록 처리하는 예제이다.

class ViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate, UITableViewDataSource, UITableViewDelegate {
    @IBOutlet weak var tableView: UITableView!

    private var activeTextField: UITextField?
    private var activeTextView: UITextView?

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
        registerForKeyboardNotifications()
    }
	... 중간 생략 ...
    
    
    // 활성화된 TextView 입력 필드를 추적 및 갱신
    func textFieldDidBeginEditing(_ textField: UITextField) {
        activeTextField = textField
        activeTextView = nil
    }
    
    func textFieldDidEndEditing(_ textField: UITextField) {
        activeTextField = nil
    }
    
    
	// 활성화된 TextView 입력 필드를 추적 및 갱신
    func textViewDidBeginEditing(_ textView: UITextView) {
        activeTextView = textView
        activeTextField = nil
    }
    
    func textViewDidEndEditing(_ textView: UITextView) {
        activeTextView = nil
    }
}

extension ViewController {
	// 키보드가 나타날 때, 키보드가 내려갈 때 이벤트 등록
    func registerForKeyboardNotifications() {
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(_:)), name: UIResponder.keyboardDidShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
    }
	
    // 키보드가 나타날 때 실행되며, 현재 활성화된 입력 필드가 키보드에 의해 가려지지 않도록 스크롤
    @objc func keyboardWasShown(_ notification: NSNotification) {
        guard let userInfo = notification.userInfo,
              let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }

        let keyboardRectangle = keyboardFrame.cgRectValue
        let keyboardHeight = keyboardRectangle.height

        let insets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
        tableView.contentInset = insets
        tableView.scrollIndicatorInsets = insets

        var aRect = self.view.frame
        aRect.size.height -= keyboardHeight

        if let activeTextField = activeTextField, let textFieldRect = activeTextField.superview?.convert(activeTextField.frame, to: tableView) {
            if !aRect.contains(textFieldRect.origin) {
                tableView.scrollRectToVisible(textFieldRect, animated: true)
            }
        } else if let activeTextView = activeTextView, let textViewRect = activeTextView.superview?.convert(activeTextView.frame, to: tableView) {
            if !aRect.contains(textViewRect.origin) {
                tableView.scrollRectToVisible(textViewRect, animated: true)
            }
        }
    }
	
    // 키보드가 내려갈 때 
    @objc func keyboardWillBeHidden(_ notification: NSNotification) {
        tableView.contentInset = .zero
        tableView.scrollIndicatorInsets = .zero
    }
}

 

반응형
블로그 이미지

SKY STORY

,
반응형

UITextField 컨트럴을 사용하여 숫자를 입력 받을 경우 입력된 숫자에 자동으로 콤마를 추가되도록 구현해 보았다. 또한 숫자 이외의 문자입력 방지 및 Int형 변환처리등을 구현하였다.

import Foundation
import UIKit

class MainViewController: UIViewController {

    @IBOutlet weak var tfAmount: UITextField!
    
    override func viewDidLoad() {
    	super.viewDidLoad()
        
        // 키보드 타입 설정 (storyboard에서 설정 가능)
        tfAmount.delegate = self
        tfAmount.keyboardType = .numbersAndPunctuation  // 숫자와 구두점이 포함된 키보드로 설정
        tfAmount.clearButtonMode = .whileEditing        // 편집하는 동안 'clear' 버튼 활성화
        tfAmount.clearsOnBeginEditing = true            // 편집 시작 시 내용 지우기
    }
    
    import UIKit

extension MainViewController : UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if textField == self.tfJJRefundAmount {
            // 숫자 이외의 문자는 입력되지 않도록 처리
            let invalidCharacters = CharacterSet.decimalDigits.inverted
            return string.rangeOfCharacter(from: invalidCharacters) == nil
        }
        return true
    }
    
    func textFieldDidChangeSelection(_ textField: UITextField) {
        if textField == self.tfAmount {
            // 입력시 자동으로 ',' 추가
            if let text = textField.text?.replacingOccurrences(of: ",", with: "") {
                if let number = Int(text) {
                    let formatter = NumberFormatter()
                    formatter.numberStyle = .decimal
                    textField.text = formatter.string(from: NSNumber(value: number))
                    self.tfAmount.tag = number // Int형 값을 tag에 저장.
                }
            }
        }
    }

    func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
        if textField == self.tfAmount && textField.text?.isEmpty == true {
            // 필요한 경우 추가 처리
        }
        return true
    }
    
    func textFieldShouldClear(_ textField: UITextField) -> Bool {
        textField.text = ""
        return true
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()    // 키보드 내리기
        
        if textField == self.tfAmount && textField.text?.isEmpty == true {
            // 필요한 경우 추가 처리
        }
        return true
    }
}

 

다음 코드는 'OK' 버튼을 선택했을 때 입력된 금액을 Int형 접근하는 사용 예이다.

extension MainViewController {
    
    @IBAction func okButtonTapped(_ sender: UIButton) {
	    guard let amountText = tfAmount.text else {
            print("금액 입력이 올바르지 않습니다.")
            return
        }
        
        // 금액 (Int형 사용을 위해 tag값 사용)
        var amount = self.tfAmount.tag 
        // 배송비
        var shippingCost = 3000 
        // 총 금액 계산
        let totalAmount = amount + shippingCost
        
        print("총 금액 : \(totalAmount)원")
    }
}

 

 

반응형
블로그 이미지

SKY STORY

,

BLE, Beacon, iBeacon

개발/Note 2024. 1. 11. 13:39
반응형

BLE, Beacon, iBeacon 각각의 차이점을 정리해 보았다.

 

  • BLE, Beacon 비교
특성 Bluetooth Low Energy (BLE) Beacon
용도     주변 기기 간의 양방향 통신 주변에 위치 정보를 브로드캐스트
통신 방식     양방향 통신 단방향 통신 (브로드캐스트)
전력 소비     저전력 통신으로 에너지 효율적 전송만 수행하여 에너지 소비 낮음                                           
주요 사용 분야     다양한 응용 분야 (데이터 교환) 위치 기반 서비스, 광고, 근거리 탐지
통신 범위     주변 기기 간의 상대적으로 높음 범위                                        주로 근거리 ( 미터 )

 

  • Beacon, iBeacon 비교
특성 Beacon iBeacon
프로토콜 Bluetooth Low Energy (BLE)  Bluetooth Low Energy (BLE) 
데이터 전송 속도 0.25Mbps ~ 24Mbps 1Mbps
전력 소비 높음 낮음
주요 사용 분야 위치 기반 서비스, 광고, 마케팅 위치 기반 서비스, 광고, 마케팅
비고   Apple 도입한 특정한 규격

 

  • Beacon

| AD Length | AD Type | Manufacturer ID | Beacon ID | Major | Minor | TX Power |

 

  • iBeacon

| AD Length | AD Type | Manufacturer ID | iBeacon Prefix | UUID | Major | Minor | TX Power |

 

  • 참고사항 :

Beacon과 iBeacon은 Advertisement Data의 AD Type으로 일반적으로 0xFF(Manufacturer Specific Data)를 사용합니다. 이 AD Type은 제조사에 특화된 데이터를 나타내며, Beacon과 iBeacon은 자체적인 프로토콜을 사용하여 이 부분에 정보를 담습니다.

따라서 Beacon이나 iBeacon으로 방송될 때, Advertisement Data의 AD Type이 0xFF로 설정되는 것이 일반적입니다. 그리고 이 AD Type 뒤에 이어지는 데이터 부분에는 Beacon이나 iBeacon에 대한 고유한 정보가 포함됩니다.

이러한 설정은 Beacon 또는 iBeacon의 프로토콜 스펙에 따라 다를 수 있으므로, 구체적인 Beacon 또는 iBeacon의 문서나 스펙을 참고하여야 합니다. Beacon과 iBeacon은 자체적으로 프로토콜을 정의하고 있기 때문에, 이러한 세부 사항은 제조사 또는 표준에 따라 다르게 정의될 수 있습니다.

 

2024.01.11 - [Note] - BLE, Beacon, iBeacon

2024.01.11 - [Note] - BLE Advertising Payload format 샘플 분석

2024.01.09 - [Note] - Packet Format for the LE Uncoded PHYs

2024.01.04 - [iOS] - Floating, Dragging Button

2023.12.27 - [생각의 조각들] - 말이 통하지 않는 사람과 싸우지 말라.

2023.12.27 - [Server] - Push Notification

2023.12.27 - [AI,ML,ALGORITHM] - A star

2023.12.07 - [iOS] - XOR 연산을 이용한 문자열 비교

2023.11.03 - [AI,ML,ALGORITHM] - Gomoku(Five in a Row, Omok) (5/5) - 3x3 체크 추가

2023.10.29 - [AI,ML,ALGORITHM] - Gomoku(Five in a Row, Omok) (5/5) - 머신러닝으로 게임 구현

 

 

 

반응형
블로그 이미지

SKY STORY

,
반응형

BLE Advertising Payload format을 raw data로 부터 분리 추출하는 방법을 알아보겠다.

raw data로 부터 payload 포맷에 대한 이해에 도움이 되길 바란다.

Flags, Manufacturer Specify Data, TX Power Level, Slave Connection Interval Range (BLE 5.0미만), Service UUIDs (Complete UUID 128 bit), Device Name, Service Data, Beacon Adv. Data, iBeacon Adv. Data 등의 포맷을 알아 보겠다.

아래 일부 샘플 자료는 아래 사이트에서 참고하였다.
https://jimmywongiot.com/2019/08/13/advertising-payload-format-on-ble/

 

Flags

Length Data Type Advertising Mode
0x02 (2) 0x01 (1) 0x06 (00000110B)

——————————————————————————————————————————
Flags
02 01 06
——————————————————————————————————————————
AD length : 0x02 (2)
AD Type : 0x01 (1)
	- 0x01 : Flags
AD data : 0x06
	- 0x06 ( 0000 0110 B ) 
	- Bit 0 : LE Limited Discoverable Mode		NO
	- Bit 1 : LE General Discoverable Mode		YES
	- Bit 2 : BR / EDR Not Supported			YES  
	- Bit 3 : Simultaneous LE and BR/EDR to Same Device Capable			NO
	- Bit 4 : Simultaneous LE and BR/EDR to Same Device Capable (Host)	NO
	- Bit 5 : Reserved
	- Bit 6 : Reserved
	- Bit 7 : Reserved

 

 

Manufacturer Specify Data

Length Data Type Company ID Manufacturer Specific Data
0x1B (27) 0xFF (255) 0x0059 0x01C0111111CC64F00A0B0C0D0E0F101112131415161718

 

 

Manufacturer Specific Data (offset) Type Value / Data
0 Header of Manufacturer Payload 0x01
1-7 MAC Address 0xCC, 0x11, 0x11, 0x11, 0x11, 0xC0
8 Battery Value in % 0x64 (100%)
9 Measured RSSI Value 0xF0
10 – 24 Other Value 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 
0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
0x16, 0x17, 0x18
——————————————————————————————————————————
Manufacturer Specific Data
1B FF 59 00 01 C0 11 11 11 11 CC 64 F0 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18
——————————————————————————————————————————
AD length : 0x1B (27)
AD Type : 0xFF (255)
	- 0xFF :  Manufacturer Specific Data
AD data :
	Company Id :
		- 0x0059 (89)
	Manufacturer Specific Data :
		- 0x01C0111111CC64F00A0B0C0D0E0F101112131415161718
		- Header of Manufacturer Payload (0) : 0x01 (1)
		- MAC Address (1-7) : 0xCC, 0x11, 0x11, 0x11, 0x11, 0xC0
		- Battery Value in % (8) : 0x64 (100) %
		- Measured RSSI Value (9) : 0xF0 (240)
		- Other Value (10-24) : 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18

 

 

TX Power Level

Length Data Type TX Power Level
0x02 (2) 0x0A (10) 0xFC (-4 dBm)

——————————————————————————————————————————
Tx Power Level
02 0A FC
——————————————————————————————————————————
AD length : 0x02 (2)
AD Type : 0x0A (10)
	- 0x0A : Tx Power Level
AD data : 0xFC (-4 dBm)
	- 0xFC ( 1111 1100 B ) 
	- 부호 있는 8비트 정수이므로 2의 보수를 취해 값을 구한다.
	- 11111100을 반전하면 00000011. 그리고 1을 더하면 00000100. 즉, 값은 -4가 된다.

 

 

Slave Connection Interval Range

Length Data Type Minimum Maximum
0x05 (5) 0x12 (18) 0x0006 (7.5ms) 0x0014 (25ms)

——————————————————————————————————————————
Slave Connection Interval Range
05 12 06 00 14 00
——————————————————————————————————————————
AD length : 0x05 (5)
AD Type : 0x12 (18)
	- 0x12 : Slave Connection Interval Range
AD data :
	- BLE 5.0 미만 버전에서 사용 됨. BLE 5.0 이상에서는 Advertising ID로 변경 됨.
	- Slave Connection Interval은 BLE(Bluetooth Low Energy)에서 두 장치 간의 연결 주기를 나타내는 값입니다.
	- Connection Interval은 두 연속적인 연결 사이의 시간 간격을 말하며, 이를 설정함으로써 연결의 성능과 전력 소비를 조절할 수 있습니다.
	- 최소 값과 최대 값은 16진수로 표현된 값이며, 단위는 1.25ms입니다. 
	- Connection Interval Min/Max (ms)=16진수 값×1.25
	Minimum : 
		- 0x0006 (6) 
		- 6 x 1.25 = 7.5 ms
	Maximum :
		- 0x0014 (20)
		- 20 x 1.25 = 25 ms

 

 

Service UUIDs (Complete UUID 128 bit)

Length Data Type UUID
0x11 (17) 0x07 (7) 0x6E400001-B5A3-F393-E0A9-E50E24DCCA9E

——————————————————————————————————————————
Complete List of 128-bit Service Class UUIDs 
11 07 9E CA DC 24 0E E5 A9 E0 93 F3 A3 B5 01 00 40 6E
——————————————————————————————————————————
AD length : 0x11 (17)
AD Type : 0x07 (7)
	- 0x12 : Complete List of 128-bit Service Class UUIDs 
AD data :
	- 0x6E400001-B5A3-F393-E0A9-E50E24DCCA9E

 

 

Complete Local Name

Length Data Type Complete Local Name
0x0C (12) 0x09 (9) Nordic_UART (0x4E6F726469635F55415254)

——————————————————————————————————————————
Complete Local Name 
0C 09 4E 6F 72 64 69 63 5F 55 41 52 54
——————————————————————————————————————————
AD length : 0x0C (12)
AD Type : 0x09 (9)
	- 0x09 : Complete Local Name 
AD data :
	- Nordic_UART (0x4E6F726469635F55415254)

 

 

Beacon Adv. Data

Length Data Type Beacon Adv. Data
0x1A (26) 0xFF (255) 0x004C 000215 112233445566778899AABBCCDD

——————————————————————————————————————————
Beacon Adv. Data
| AD Length | AD Type | Manufacturer ID | Beacon ID | Major | Minor | TX Power |
1A FF 00 4C 00 02 15 11 22 33 44 55 66 77 88 99 AA BB CC DD
——————————————————————————————————————————
AD length : 0x1A (26)
AD Type : 0xFF (255)
	- 0xFF : Manufacturer Specific Data
AD data :
	Manufacturer :
	- 0x004C (Beacon을 제조한 회사의 ID)
	Beacon ID :
	- 0x000215 (Beacon을 식별하는 고유 ID)
	Beacon Data :
	- 0x112233445566778899AABBCCDD (Beacon 고유 데이터)
	- Beacon Data의 세부 사항은 제조사 또는 표준에 따라 다르게 정의될 수 있다. 
	- 일반적으로 Major, Minor, TX Power를 사용한다.

 

 

iBeacon Adv. Data

Length Data Type iBeacon Adv. Data
0x1F (31) 0xFF (255) 0x4C 000215 E2C56DB5DFFB48D2B060D0F5A71096E0 0001 0002 C5

——————————————————————————————————————————
iBeacon Adv. Data
| AD Length | AD Type | Manufacturer ID | iBeacon Prefix | UUID | Major | Minor | TX Power |
1F FF 4C 00 02 15 E2C56DB5DFFB48D2B060D0F5A71096E0 0001 0002 C5
——————————————————————————————————————————
AD length : 0x1A (26)
AD Type : 0xFF (255)
	- 0xFF : Manufacturer Specific Data
AD data : 
	Manufacturer :
	- 0x4C (Beacon을 제조한 회사의 ID)
	iBeacon Prefix :
	- 0x000215 (Apple이 iBeacon을 식별하기 위한 특별한 값)
	UUID : 
	- 0xE2C56DB5DFFB48D2B060D0F5A71096E0 (16바이트의 Universally Unique Identifier)
	Major :
	- 0x0001 (주요 범주를 식별하는 값)
	Minor :
	- 0x0002 (Major 범주 내에서 세부 항목을 식별하는 값)
	TX Power :
	- 0xC5 (전송 전력을 나타내는 값)

 

 


 

 

※ Flags, Service Data, Complete Local Name

Flags

Length Data Type Service Data
0x02 (2) 0x01 (1) 0x06

Service Data

Length Data Type Service Data
0x05 (5) 0x16 (22) 0x6E2ADB02

Complete Local Name

Length Data Type Service Data
0x0B (11) 0x09 (9) 0x50205420383034394638                                 

——————————————————————————————————————————
Flags
02 01 06
——————————————————————————————————————————
AD length : 0x02 (2)
AD Type : 0x01 (1)
	- 0x01 : Flags
AD data : 0x06
	- 0x06 ( 0000 0110 B ) 
	- Bit 0 : LE Limited Discoverable Mode		NO
	- Bit 1 : LE General Discoverable Mode		YES
	- Bit 2 : BR / EDR Not Supported				YES  
	- Bit 3 : Simultaneous LE and BR/EDR to Same Device Capable			NO
	- Bit 4 : Simultaneous LE and BR/EDR to Same Device Capable (Host)	NO
	- Bit 5 : Reserved
	- Bit 6 : Reserved
	- Bit 7 : Reserved


——————————————————————————————————————————
Service Data 16-bit UUID
05 16 6E 2A DB 02
——————————————————————————————————————————
AD length : 0x05 (5)
AD Type : 0x16 (22)
	- 0x16 :  Service Data 16-bit UUID
AD data :
	- 0x6E2ADB02
	- ServiceData의 값은 해당 서비스의 사양에 따라 정의된다. (사용자 설정 포맷)
	- ServiceData UUID를 포함할 경우 [CBUUID: Data] 포맷으로 만들기도 한다.


——————————————————————————————————————————
Complete Local Name
0B 09 50 20 54 20 38 30 34 39 46 38
——————————————————————————————————————————
AD length : 0x0B (11)
AD Type : 0x09 (9)
	- 0x09 :  Complete Local Name
AD data :
	- 0x50205420383034394638
	- ASCII 문자열로 변환하면  "P T 8 0 4 9 F 8" 이다.

 

 

 

2024.01.11 - [Note] - BLE, Beacon, iBeacon

2024.01.11 - [Note] - BLE Advertising Payload format 샘플 분석

2024.01.09 - [Note] - Packet Format for the LE Uncoded PHYs

2024.01.04 - [iOS] - Floating, Dragging Button

2023.12.27 - [Bark Bark] - 말이 통하지 않는 사람과 싸우지 말라.

2023.12.27 - [Server] - Push Notification

2023.12.27 - [AI,ML,ALGORITHM] - A star

2023.12.07 - [iOS] - XOR 연산을 이용한 문자열 비교

2023.11.03 - [AI,ML,ALGORITHM] - Gomoku(Five in a Row, Omok) (5/5) - 3x3 체크 추가

2023.10.29 - [AI,ML,ALGORITHM] - Gomoku(Five in a Row, Omok) (5/5) - 머신러닝으로 게임 구현2023.11.03 - [AI,ML,ALGORITHM] - Gomoku(Five in a Row, Omok) (5/5) - 3x3 체크 추가

 

 

반응형

'개발 > Note' 카테고리의 다른 글

git 마지막 commit 취소, 메시지 수정  (0) 2024.10.16
BLE, Beacon, iBeacon  (0) 2024.01.11
Packet Format for the LE Uncoded PHYs  (0) 2024.01.09
Push Notification  (1) 2023.12.27
[Mac OS X] Apache Virtual Hosts 설정 설정  (0) 2023.02.21
블로그 이미지

SKY STORY

,
반응형
반응형

'개발 > Note' 카테고리의 다른 글

BLE, Beacon, iBeacon  (0) 2024.01.11
BLE Advertising Payload format 샘플 분석  (1) 2024.01.11
Push Notification  (1) 2023.12.27
[Mac OS X] Apache Virtual Hosts 설정 설정  (0) 2023.02.21
Unwind Segue 사용방법  (0) 2023.01.11
블로그 이미지

SKY STORY

,
반응형

뷰 화면 내에서 플로팅 및 드레깅 가능한 버튼을 만들어 보았다.

draggableView 사이즈는 50x50으로 설정하였다.

 

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var draggableView: UIView!
    
    var isStickyEffect: Bool! = false       // 좌우측으로 자동으로 벽에 붙도록 처리 flag
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 팬 제스처 및 탭 제스처 추가
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handler))
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(draggableViewTapped))
        
        draggableView.addGestureRecognizer(panGesture)
        draggableView.addGestureRecognizer(tapGesture)
    }
    
    @objc func handler(gesture: UIPanGestureRecognizer) {
        let location = gesture.location(in: self.view)
        let draggedView = gesture.view
        
        // 뷰의 반너비와 반높이 계산
        let halfWidth = draggedView!.bounds.width / 2
        let halfHeight = draggedView!.bounds.height / 2
        
        // Safe Area를 고려한 화면 경계에 도달하면 뷰를 화면 안쪽으로 이동시킴
        let minX = halfWidth
        let maxX = view.safeAreaLayoutGuide.layoutFrame.width - halfWidth
        let minY = view.safeAreaInsets.top + halfHeight
        let maxY = view.bounds.height - halfHeight
        
        // 좌표 제한
        draggedView?.center.x = max(minX, min(location.x, maxX))
        draggedView?.center.y = max(minY, min(location.y, maxY))
        
        // 드레그 상태 종료시 이벤트
        if gesture.state == .ended {
            stickyEffect()
        }
    }
}



extension ViewController {
    // 좌우측으로 자동으로 벽에 붙도록 처리
    func stickyEffect() {
        let halfWidth = draggableView.bounds.width / 2
        if isStickyEffect == true {
            // 이동이 끝났을 때 특별한 효과 없이 뷰가 그 자리에 고정됨
            if self.draggableView.frame.midX >= self.view.layer.frame.width / 2 {
                UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: {
                    self.draggableView.center.x = self.view.layer.frame.width - halfWidth//40
                }, completion: nil)
            } else {
                UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: {
                    self.draggableView.center.x = halfWidth//40
                }, completion: nil)
            }
        }
    }
    
    // 버튼 텝 이벤트
    @objc func draggableViewTapped() {
        let message = "버튼 텝 이벤트"
        let alertController = UIAlertController(title: "Alert", message: message, preferredStyle: .alert)
        let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
        alertController.addAction(okAction)
        present(alertController, animated: true, completion: nil)
    }
}

 

 

 

2024.01.11 - [Note] - BLE, Beacon, iBeacon

2024.01.11 - [Note] - BLE Advertising Payload format 샘플 분석

2024.01.09 - [Note] - Packet Format for the LE Uncoded PHYs

2024.01.04 - [iOS] - Floating, Dragging Button

2023.12.27 - [Bark Bark] - 말이 통하지 않는 사람과 싸우지 말라.

2023.12.27 - [Server] - Push Notification

2023.12.27 - [AI,ML,ALGORITHM] - A star

2023.12.07 - [iOS] - XOR 연산을 이용한 문자열 비교

2023.11.03 - [AI,ML,ALGORITHM] - Gomoku(Five in a Row, Omok) (5/5) - 3x3 체크 추가

2023.10.29 - [AI,ML,ALGORITHM] - Gomoku(Five in a Row, Omok) (5/5) - 머신러닝으로 게임 구현

 

반응형
블로그 이미지

SKY STORY

,
반응형
반응형
블로그 이미지

SKY STORY

,

Push Notification

개발/Note 2023. 12. 27. 16:42
반응형

python으로 간단한 모바일 푸시 서버를 만들어 보았다.

예약 발송 및 batch 처리등 처리 하였다. 

 

#
# push_server.py
# APNs, FCM, 예약 및 batch 푸시 발송
#

import json
import jwt
import time
from datetime import datetime
from hyper import HTTPConnection
from http.client import HTTPSConnection
from apscheduler.schedulers.blocking import BlockingScheduler
import mysql.connector
import configparser
from apscheduler.events import EVENT_JOB_EXECUTED

# Read configuration from config.ini
config = configparser.ConfigParser()
config.read('config.ini')

# SSL/TLS (Secure Sockets Layer / Transport Layer Security)
CA_FILE = config.get('SSL', 'ca_file', fallback=None)
CERT_FILE = config.get('SSL', 'cert_file', fallback=None)
KEY_FILE = config.get('SSL', 'key_file', fallback=None)

# Database
DB_HOST = config.get('Database', 'host')
DB_USER = config.get('Database', 'user')
DB_PASSWORD = config.get('Database', 'password')
DB_NAME = config.get('Database', 'database')
DB_PORT = config.get('Database', 'port')

# Protocol
PROTOCOL = config.get('Server', 'protocol', fallback='HTTP')

# APNs status codes
status_codes = {
    200: "Success",
    400: "Bad request",
    403: "Error with certificate or provider authentication token",
    405: "Invalid request method. Only POST requests are supported.",
    410: "Device token is no longer active for the topic.",
    413: "Notification payload was too large.",
    429: "Too many requests for the same device token.",
    500: "Internal server error",
    503: "Server is shutting down and unavailable.",
}

conn_class = HTTPConnection if PROTOCOL.upper() == 'HTTP' else HTTPSConnection

scheduler = BlockingScheduler()


# Create the request headers with expiration time
def create_request_headers(section_name, remaining_seconds):
    push_type = config.get(section_name, 'push_type')
    
    if push_type == "APNs": 
        return create_apns_request_headers(section_name, remaining_seconds)
    elif push_type == "FCM":
        return create_fcm_request_headers(section_name)
    else:
        return None

# Create APNs request headers
def create_apns_request_headers(section_name, remaining_seconds):
    auth_key_path = config.get(section_name, 'auth_key_path')
    team_id = config.get(section_name, 'team_id')
    key_id = config.get(section_name, 'key_id')
    bundle_id = config.get(section_name, 'bundle_id')
    
    with open(auth_key_path) as f:
        secret = f.read()
    
    token = jwt.encode(
        {
            'iss': team_id,
            'iat': time.time(),
        },
        secret,
        algorithm='ES256',
        headers={
            'alg': 'ES256',
            'kid': key_id,
        }
    )
    token_bytes = token.encode('ascii')
    
    headers = {
        'apns-priority': '10',
        'apns-topic': bundle_id,
        'authorization': 'bearer {0}'.format(token_bytes.decode('ascii'))
    }
    
    if remaining_seconds is not None:
        headers['apns-expiration'] = str(remaining_seconds)

    return headers

# Create FCM request headers
def create_fcm_request_headers(section_name):
    api_key = config.get(section_name, 'api_key') 
    
    headers = {
        'Authorization': 'key=' + api_key,
        'Content-Type': 'application/json'
    }
    return headers

# Function to send notification with scheduled time
def send_notification(conn, section_name, push_token, message, badge_count=0, remaining_seconds=0):
    request_headers = create_request_headers(section_name, remaining_seconds)

    push_type = config.get(section_name, 'push_type')

    payload_data = create_payload_data(push_type, message, badge_count)
    payload = json.dumps(payload_data).encode('utf-8')

    if push_type == "APNs": 
        conn.request('POST', f'/3/device/{push_token}', payload, headers=request_headers)
    elif push_type == "FCM":
        conn.request('POST', '/fcm/send', payload, headers=request_headers)
    
    resp = conn.get_response()

    status_code = resp.status
    description = status_codes.get(status_code, "Unknown status code")
    print(f"Status code: {status_code} - Description: {description}")
    print(resp.read())

# Create payload data based on push type
def create_payload_data(push_type, message, badge_count):
    if push_type == "APNs":
        return {
            'aps': {
                'alert': message,
                'badge': badge_count,
            }
        }
    elif push_type == "FCM":
        return {
            'to': push_token,
            'notification': {
                'title': '',
                'body': message,
            },
            'data': {
                'badge': badge_count,
            },
        }

# Function to send push with scheduled time
def send_push(conn, section_name, push_token, message, badge_count, scheduled_time, remaining_seconds=0):
    scheduler.add_listener(lambda event: scheduler.shutdown(wait=False), EVENT_JOB_EXECUTED)
    scheduler.add_job(
        send_notification,
        args=(conn, section_name, push_token, message, badge_count, remaining_seconds),
        trigger='date',
        run_date=datetime.fromtimestamp(scheduled_time)
    )
    scheduler.start()

# Function to send bulk push
def send_bulk_push(conn, section_name, users, message, badge_count, scheduled_time, remaining_seconds=0):
    scheduler.add_listener(lambda event: scheduler.shutdown(wait=False), EVENT_JOB_EXECUTED)
    for user in users: 
        push_token = user[6]
        scheduler.add_job(
            send_notification,
            args=(conn, section_name, push_token, message, badge_count, remaining_seconds),
            trigger='date',
            run_date=datetime.fromtimestamp(scheduled_time)
        )
    scheduler.start()

# Function to send push to users in batches
def send_push_to_users_in_batches(section_name, users, batch_size, message, badge_count, scheduled_time, remaining_seconds=0): 
    push_type = config.get(section_name, 'push_type')
    environment = config.get(section_name, 'environment', fallback='development')

    if push_type == "APNs": 
        push_url = 'api.development.push.apple.com' if environment == 'development' else 'api.push.apple.com'
    elif push_type == "FCM":
        push_url = 'fcm.googleapis.com'

    conn = conn_class(push_url + ':443', ca_certs=CA_FILE, cert_file=CERT_FILE, key_file=KEY_FILE)

    total_users = len(users)
    num_batches = (total_users + batch_size - 1) // batch_size
    for batch_number in range(num_batches):
        start_index = batch_number * batch_size
        end_index = (batch_number + 1) * batch_size
        batch_users = users[start_index:end_index]
        send_bulk_push(conn, section_name, batch_users, message, badge_count, scheduled_time, remaining_seconds)

    conn.close()

# MySQL connection and get user info
def get_users_from_database():
    connection_params = {
        'host': DB_HOST,
        'user': DB_USER,
        'password': DB_PASSWORD,
        'database': DB_NAME,
        'port': DB_PORT,
    }
    
    if PROTOCOL == 'HTTPS':
        connection_params['ssl'] = {
            'ca': CA_FILE,
            'cert': CERT_FILE,
            'key': KEY_FILE
        }
    
    connection = mysql.connector.connect(**connection_params)
    
    try:
        cursor = connection.cursor()
        query = 'SELECT * FROM users'
        cursor.execute(query)
        users = cursor.fetchall()
        return users
    finally:
        cursor.close()
        connection.close()

# Calculate the scheduled time (in seconds from now)
def get_push_schedule_time(year=None, month=None, day=None, hour=None, minute=None):
    current_time = datetime.now()

    if all(x is None for x in [year, month, day, hour, minute]):
        return int(current_time.timestamp())

    scheduled_time = datetime(
        year if year is not None else current_time.year,
        month if month is not None else current_time.month,
        day if day is not None else current_time.day,
        hour if hour is not None else current_time.hour,
        minute if minute is not None else current_time.minute
    )

    if scheduled_time < current_time:
        return int(current_time.timestamp())

    return int(scheduled_time.timestamp())

# Calculate the remaining expiration time (in seconds from now)
def get_remaining_expiry_seconds(year=None, month=None, day=None, hour=None, minute=None):
    current_time = datetime.now()
    expiration_time = datetime(
        year if year is not None else current_time.year,
        month if month is not None else current_time.month,
        day if day is not None else current_time.day,
        hour if hour is not None else current_time.hour,
        minute if minute is not None else current_time.minute
    )
    time_difference = expiration_time - current_time
    remaining_seconds = max(int(time_difference.total_seconds()), 0)
    return remaining_seconds




# Push scheduling time
scheduled_time = get_push_schedule_time(2023, 12, 19, 10, 37)
# Remaining seconds until push expiration time
remaining_seconds = get_remaining_expiry_seconds(2023, 12, 19, 10, 38)
print(f"Scheduled Time: {scheduled_time}")
print(f"Remaining Seconds: {remaining_seconds}")

# Section name (company name)
section_name = 'MyApp'
# Get user information
users_data = get_users_from_database()
# Batch size
batch_size = 1000
# Push message
message = "test message."
# Badge count
badge_count = 1

# Send push to users based on batch size
send_push_to_users_in_batches(section_name, users_data, batch_size, message, badge_count, scheduled_time, remaining_seconds)

print("Complete.")

 

#
# config.ini
#

[Server]
# HTTP 또는 HTTPS
protocol = HTTP

[SSL]
ca_file = cert/ca.pem
cert_file = cert/cert.pem
key_file = cert/key.pem

[Database]
host = 127.0.0.1
user = netcanis
password = 12345678
database = My_DB_Name
port = 3306

[HarexApp]
# APNs, FCM
push_type = APNs
# development 또는 production
environment = development
bundle_id = com.mycompany.test
auth_key_path = cert/mycompany/AuthKey.p8
key_id = XX8UHXF9XX
team_id = XX6KEXF2XX

[HarexApp2]
# APNs, FCM
push_type = FCM
# development 또는 production
environment = development
bundle_id = com.mycompany.test
api_key = XXXX.....XXXX

 

2024.01.11 - [Note] - BLE, Beacon, iBeacon

2024.01.11 - [Note] - BLE Advertising Payload format 샘플 분석

2024.01.09 - [Note] - Packet Format for the LE Uncoded PHYs

2024.01.04 - [iOS] - Floating, Dragging Button

2023.12.27 - [생각의 조각들] - 말이 통하지 않는 사람과 싸우지 말라.

2023.12.27 - [Server] - Push Notification

2023.12.27 - [AI,ML,ALGORITHM] - A star

2023.12.07 - [iOS] - XOR 연산을 이용한 문자열 비교

2023.11.03 - [AI,ML,ALGORITHM] - Gomoku(Five in a Row, Omok) (5/5) - 3x3 체크 추가

2023.10.29 - [AI,ML,ALGORITHM] - Gomoku(Five in a Row, Omok) (5/5) - 머신러닝으로 게임 구현

 

반응형

'개발 > Note' 카테고리의 다른 글

BLE Advertising Payload format 샘플 분석  (1) 2024.01.11
Packet Format for the LE Uncoded PHYs  (0) 2024.01.09
[Mac OS X] Apache Virtual Hosts 설정 설정  (0) 2023.02.21
Unwind Segue 사용방법  (0) 2023.01.11
랜덤 seed 초기화  (0) 2022.11.18
블로그 이미지

SKY STORY

,