intdash SDK for Swift

ドキュメントホーム

■ このSDKを使用するために必要な開発環境

  • Xcode 11.3以上

■ 動作環境

  • iOS 12以上

■ 事前準備

1. intdashサーバーの管理者からOAuth2.0のクライアントIDを入手します。
(例:abcdefg123456
2. これから開発するアプリケーションのコールバックスキーム名を決め、intdashサーバーの管理者へスキーム名の登録を依頼してください。
(例: companyname.appname
3. Info.plistURL Typesで、以下のようにコールバックスキームを登録します。
例)

|Key                |Type       |Value                             |
|-------------------|-----------|----------------------------------|
|- URL types        |Array      |                                  |
| - Item 0 (Viewer) |Dictionary |                                  |
|  - Document Role  |String     |Viewer                            |
|  - URL identifier |String     |$(PRODUCT_BUNDLE_IDENTIFIER)      |
|  - URL Schemes    |Array      |                                  |
|   - Item 0        |String     |スキーム名(例:companyname.appname)   |

4. Intdash.xcframework をプロジェクトに追加します。

※ 手動で開発するプロジェクトへフレームワークを追加した場合は Embed & Sign としてください。

Cocoapods

このフレームワークは CocoaPods からも入手可能です、Podfile に以下を追加してください。

pod 'Intdash'

■ 実装

・認証

1. フレームワークをインポートする。
2. IntdashClient.Session を初期化する。
3. IntdashClient.OAuth2API を初期化する。
4. Web認証用URLを生成する。
5. 認証を開始する。
6. 認証結果が正しいかチェックする。
7. 正常に認証ができていれば認証コードを利用してアクセストークンを取得する。
8. 必要に応じて、サインインしたエッジ(自分自身)の情報を取得する。

// 1. フレームワークをインポートする。
import Intdash
import AuthenticationServices

// intdashサーバー名
let kTargetServer: String = "https://example.com"
// OAuth2.0 クライアントID
let kIntdashClientId: String = "abcdefg123456"
// コールバックスキームを使用したコールバックURL
let kCallbackURLScheme: String = "companyname.appname://oauth2/callback"

class ExampleViewController: UIViewController {

    var session: IntdashClient.Session?
    var signInEdgeUuid: String?
    var signInEdgeName: String?

    private var webAuthSession: NSObject?

    func signIn() {
        guard #available(iOS 12.0, *) else {
            print("Unsupported OS")
            return
        }
        // 2. `IntdashClient.Session` を初期化する。
        let session = IntdashClient.Session(serverURL: kTargetServer, clientId: kIntdashClientId)
        self.session = session
        // 3. `IntdashClient.OAuth2API` を初期化する。
        let oauth2Api = IntdashClient.OAuth2API(session: session)
        // 4. Web認証用URLを生成する。
        let callbackURLScheme = kCallbackURLScheme.replacingOccurrences(of: ":", with: "%3A").replacingOccurrences(of: "/", with: "%2F") // URLエンコード
        oauth2Api.generateAuthorizationURL(callbackURLScheme: callbackURLScheme) { [weak self] (url, codeVerifier, state, error) in
            guard error == nil, let url = url, let authURL = URL(string: url), let codeVerifier = codeVerifier else {
                print("generateAuthorizationURL failed. \(error?.localizedDescription ?? "")")
                return
            }
            // 5. 認証を開始する。
            let webAuthSession = ASWebAuthenticationSession(url: authURL, callbackURLScheme: callbackURLScheme) { (callbackURL, error) in
                guard error == nil, let callbackURL = callbackURL else {
                    print("Web authentication callback error. \(error?.localizedDescription ?? "")")
                    return
                }
                // 6. 認証結果が正しいかチェックする。
                var result = false
                var code: String?
                if let queryItems = URLComponents(string: callbackURL.absoluteString)?.queryItems {
                    for item in queryItems {
                        if item.name == "state", item.value == state {
                            result = true
                        }
                        if item.name == "code" {
                            code = item.value
                        }
                    }
                }
                if let code = code, result {
                    print("Web authentication was successful.")
                    // 7. 正常に認証ができていれば認証コードを利用してアクセストークンを取得する。
                    // (※このとき `IntdashClient.Session` の認証情報はフレームワーク側で自動で更新されます。)
                    oauth2Api.authenticate(code: code, codeVerifier: codeVerifier, callbackURLScheme: kCallbackURLScheme) { (response, error) in
                        if let error = error {
                            print("requestAccessToken failed. \(error.localizedDescription)")
                            return
                        }
                        // 8. 必要に応じて、サインインしたエッジ(自分自身)の情報を取得する。
                        // (※このあとデータのアップストリームを行う場合はエッジのUUIDが必要です。)
                        let edgesApi = IntdashClient.EdgesAPI(session: session)
                        edgesApi.me { (response, error) in
                            guard let response = response else {
                                print("requestEdgesMe failed. \(error?.localizedDescription ?? "")")
                                return
                            }
                            print("Successful sign-in.")
                            self?.signInEdgeName = response.name
                            self?.signInEdgeUuid = response.uuid
                        }
                    }
                }
            }
            if #available(iOS 13.0, *) {
                webAuthSession.presentationContextProvider = self
                webAuthSession.prefersEphemeralWebBrowserSession = false
            }

            // Start
            self?.webAuthSession = webAuthSession
            webAuthSession.start()
        }
    }
}

extension ExampleViewController: ASWebAuthenticationPresentationContextProviding {

    @available(iOS 12.0, *)
    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        return UIApplication.shared.windows.first ?? ASPresentationAnchor()
    }

}


・エッジ、計測、キャプチャーなどintdash APIが提供するリソースの取得

例として、ここではエッジの一覧を取得します。

1. IntdashClient を初期化する。
2. 認証情報がセットされた IntdashClient.Session をセットする。
3. エッジの一覧を取得する。

// 1. IntdashClientを初期化する。
let intdash = IntdashClient()

// 2. 認証情報がセットされた `IntdashClient.Session` をセットする。
intdash.session = self.session

// 3. エッジの一覧を取得する。
intdash.edges.list { (response, error) in
    guard let response = response else {
        print("requestEdgeList failed. \(error?.localizedDescription ?? "")")
        return
    }
    print("successful request for edge list. \(response.items.count) edges")
    for item in response.items {
        print("\(item.name) [\(item.uuid) ")
    }
}

・リアルタイムデータのダウンストリームを行う

1. IntdashClient を初期化する。
2. 認証情報がセットされた IntdashClient.Session をセットする。
3. IntdashClient.DownstreamManager のデリゲートを設定する。
4. intdashサーバーとの接続を開始する。
5. ダウンストリームを開く。
6. ダウンストリームフィルター(IntdashClient.DownstreamManager.RequestFilters)を生成する。
7. ダウンストリーム情報をintdashサーバーと同期する。

var intdash: IntdashClient?
var downstreamIds: [Int]?

/// ダウンストリームを行う対象のエッジのUUID
var downstreamTargetEdgeUuid = "" // 必要に応じて変更
/// ダウンストリームを行う対象のエッジのチャンネル番号
var targetChannel: Int = 1 // 必要に応じて変更

func startDownsteram() {
    // 1. `IntdashClient` を初期化する。
    let intdash = IntdashClient()
    // 2. 認証情報がセットされた `IntdashClient.Session` をセットする。
    intdash.session = self.session
    self.intdash = intdash
    // 3. `IntdashClient.DownstreamManager` のデリゲートを設定する。
    intdash.downstreamManager.addDelegate(delegate: self) // IntdashClientDownstreamManagerDelegate
    // 4. intdashサーバーとの接続を開始する。
    intdash.connect { [weak self] (error) in
        if let error = error {
            print("Failed to connect to the intdash server. \(error.localizedDescription)")
            return
        }
        // 5. ダウンストリームを開く。
        let streamId: Int
        do {
            streamId = try intdash.downstreamManager.open(srcEdgeId: self!.downstreamTargetEdgeUuid)
        } catch {
            print("Failed to open downstream. \(error)")
            return
        }
        if self?.downstreamIds == nil {
            self?.downstreamIds = [streamId]
        } else {
            self?.downstreamIds?.append(streamId)
        }
        // 6. ダウンストリームフィルター(`IntdashClient.DownstreamManager.RequestFilters`)を生成する。
        let downstreamFilter = self?.makeDownstreamFilters(streamId: streamId, channel: self!.targetChannel)
        // 7. ダウンストリーム情報をintdashサーバーと同期する。(※downstreamFiltersにnilを指定すると、全チャンネル、全データタイプ、全IDのデータを受信します)
        intdash.downstreamManager.sync(completion: { (errors) in
            if let errors = errors {
                print("Failed to request dowsntream. \(errors)")
                return
            }
            print("Success to downstream request.")
        }, filters: downstreamFilter)
    }
}

ダウンストリームフィルターの生成方法

8. IntdashClient.DownstreamManager.RequestFilters を初期化する。
9. ダウンストリームするデータの情報を追加する。

func makeDownstreamFilters(streamId: Int, channel: Int) -> IntdashClient.DownstreamManager.RequestFilters? {
    // 8. `IntdashClient.DownstreamManager.RequestFilters` を初期化する。
    let downstreamFilters = IntdashClient.DownstreamManager.RequestFilters()

    // 9. ダウンストリームするデータの情報を追加する。
    // フィルターが必要ない(全チャンネル、データを対象とする)場合は何もappendしない。
    // 「チャンネル1、データタイプGeneralSensor、ID 1,3,4」を追加する場合
    downstreamFilters.append(streamId: streamId, channelNum: 1, dataType: .generalSensor, ids: [1, 3, 4])
    // 「チャンネル1、データタイプH.264、IDなし」を追加する場合
    downstreamFilters.append(streamId: streamId, channelNum: channel, dataType: .h264, id: nil)

    // フィルターが追加されなかった場合は全開放ファイルターとして扱われます。
    return downstreamFilters
}  

ダウンストリームされたデータの取得方法

10. IntdashClientDownstreamManagerDelegate.downstreamManagerDidParseDataPoints から RealtimeDataPoint の配列を取得する。

extension ExampleViewController: IntdashClientDownstreamManagerDelegate {

    // 10. `IntdashClientDownstreamManagerDelegate.downstreamManagerDidParseDataPoints` から `RealtimeDataPoint` の配列を取得する。
    func downstreamManagerDidParseDataPoints(_ manager: IntdashClient.DownstreamManager, streamId: Int, dataPoints: [RealtimeDataPoint]) {
        for dataPoint in dataPoints {
            switch dataPoint.dataModel.dataType {
                // ...
            default: break
            }
        }
    }

}

ダウンストリームを終了する

11. ダウンストリームを閉じる。
12. 閉じられているダウンストリームを削除する。
13. intdashサーバーとの接続を終了する。(※終了する場合)

func stopDownstream() {
    guard let intdash = self.intdash else { return }
    self.intdash = nil
    let group = DispatchGroup()
    if let streamIds = self.downstreamIds {
        self.downstreamIds = nil
        group.enter()
        DispatchQueue.global().async {
            // 11. ダウンストリームを閉じる。
            intdash.downstreamManager.close(streamIds: streamIds) { (error) in
                if let error = error {
                    print("Failed to close downstream. \(error.localizedDescription)")
                } else {
                    print("Success to close downstream.")
                }
                // 12. 閉じられたダウンストリームを削除する。
                intdash.downstreamManager.removeClosedDownstream()
                group.leave()
            }
        }
    }

    group.notify(queue: .global()) {
        // 13. intdashサーバーとの接続を終了する。(※終了する場合)
        intdash.disconnect { (error) in
            if let error = error {
                print("Failed to disconnect to the intdash server. \(error.localizedDescription)")
            } else {
                print("Success to disconnect to the intdash server.")
            }
        }
    }
}

・リアルタイムデータのアップストリームを行う(新しい計測を開始し、サーバーにリアルタイムデータを送信する)

1. IntdashClient を初期化する。
2. 認証情報がセットされた IntdashClient.Session をセットする。
3. IntdashClient.UpstreamManager のデリゲートを設定する。(※セクション情報の管理やAckの確認を行いたい場合のみ)
4. intdashサーバーとの接続を開始する。
5. 計測IDを取得する。
6. アップストリームを開く。
7. アップストリーム情報をintdashサーバーと同期する。
8. iOSデバイス内にデータを保存する場合は IntdashDataFileManager を初期化する。
9. iOSデバイス内にデータを保存する場合は IntdashDataFileManager.setMeasurementId() で計測IDを保存する。

//var intdash: IntdashClient?
var upstreamId: Int?
var upstreamIds: [Int]?

/// アップストリームを行うチャンネル番号(※ストリームごとに別のチャンネル番号を設定することができます)
//var targetChannel: Int = 1 // 必要に応じて変更

/// iOSデバイス内にデータを保存する場合に使用するファイルマネージャー
var intdashDataFileManager: IntdashDataFileManager?
/// intdashサーバーへ保存するかの選択
var isSaveToServer: Bool = true
/// intdashデータを保存するディレクトリのパス
var intdashDataFileParentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]

func startUpstream() {
    guard let edgeUuid = self.signInEdgeUuid else { return }
    // 1. `IntdashClient` を初期化する。
    let intdash = IntdashClient()
    self.intdash = intdash
    // 2. 認証情報がセットされた `IntdashClient.Session` をセットする。
    intdash.session = self.session
    // 3. IntdashClient.UpstreamManagerのデリゲートを設定する。(※セクション情報の管理やAckの確認を行いたい場合のみ)
    intdash.upstreamManager.addDelegate(delegate: self) // IntdashClientUpstreamManagerDelegate
    // 4. intdashサーバーとの接続を開始する。
    intdash.connect { [weak self] (error) in
        if let error = error {
            print("Failed to connect to the intdash server. \(error.localizedDescription)")
            return
        }
        // 5. 計測IDを取得する。
        intdash.upstreamManager.requestMeasurementId(edgeUuid: edgeUuid) { [weak self] (measurementId, error) in
            guard let measurementId = measurementId else {
                print("Failed to requestMeasurementId. \(error?.localizedDescription ?? "")")
                return
            }
            // 6. アップストリームを開く。
            let streamId: Int
            do {
                // データをサーバーに保存する場合は `store` を `true` にしてください。
                streamId = try intdash.upstreamManager.open(measurementId: measurementId, srcEdgeId: edgeUuid, store: self!.isSaveToServer)
            } catch {
                print("Failed to open upstream. \(error)")
                return
            }
            self?.upstreamId = streamId
            if self?.upstreamIds == nil {
                self?.upstreamIds = [streamId]
            } else {
                self?.upstreamIds?.append(streamId)
            }
            // 7. アップストリーム情報をintdashサーバーと同期する。
            intdash.upstreamManager.sync { [weak self] (error) in
                if let error = error {
                    print("Failed to request upstream. \(error)")
                    return
                }
                print("Success to request upstream.")
                if self!.isSaveToServer {
                    do {
                        // 8. iOSデバイス内にデータを保存する場合は `IntdashDataFileManager` を初期化する。
                        let fileManager = try IntdashDataFileManager(parentPath: "\(self!.intdashDataFileParentPath)/\(measurementId)")
                        // 9. iOSデバイス内にデータを保存する場合は `IntdashDataFileManager.setMeasurementId()` で計測IDを保存する。
                        try fileManager.setMeasurementId(id: measurementId)
                        self?.intdashDataFileManager = fileManager
                    } catch {
                        print("Failed to setup file manager. \(error.localizedDescription)")
                    }
                }
            }
        }
    }
}

extension ExampleViewController: IntdashClientUpstreamManagerDelegate {

    func upstreamManager(_ manager: IntdashClient.UpstreamManager, didGeneratedSesion sectionId: Int, sectionIndex: Int, streamId: Int, final: Bool, sentCount: Int, startOfElapsedTime: TimeInterval, endOfElapsedTime: TimeInterval) {
    }

    func upstreamManager(_ manager: IntdashClient.UpstreamManager, didReceiveEndOfSection sectionId: Int, streamId: Int, success: Bool, final: Bool, sentCount: Int) {
    }

}

アップストリームするデータの送信

10. iOSデバイス内にデータを保存する場合は、計測開始時刻を IntdashDataFileManager.setBaseTime() で保存しておく。
11. 基準となる計測開始時刻を IntdashClient.UpstreamManager.sendFirstData() で送信する。
12. 送信したいデータを、 IntdashData のサブクラスでラップする。
13. iOSデバイス内にデータを保存する場合は IntdashDataFileManager.write() でデータを保存する。
14. 生成したデータを IntdashClient.UpstreamManager.sendUnit() で送信する。
15. 12、(13)、14を任意の時間または回数だけ繰り返す。

var baseTime: TimeInterval = -1

func sendFirstData(baseTime: TimeInterval, streamId: Int, channel: Int) {
    self.baseTime = baseTime
    guard let intdash = self.intdash else { return }
    do {
        // 10. iOSデバイス内にデータを保存する場合は、計測開始時刻を `IntdashDataFileManager.setBaseTime()` で保存しておく。
        try self.intdashDataFileManager?.setBaseTime(time: baseTime)
        // 11. 基準となる計測開始時間を `IntdashClient.UpstreamManager.sendFirstData()` で送信する。
        try intdash.upstreamManager.sendFirstData(baseTime, streamId: streamId, channelNum: channel)
    } catch {
        print("Failed to send first data. \(error.localizedDescription)")
        return
    }
}

func generateData() {
    guard let streamId = self.upstreamId else { return }
    let timestamp = Date().timeIntervalSince1970
    if self.baseTime == -1 {
        self.sendFirstData(baseTime: timestamp, streamId: streamId, channel: self.targetChannel)
    }
    let value: Int = 1
    // 12. 送信したいデータを、 `IntdashData` のサブクラスでラップする。
    // データの種別に関しては「詳説iSCP 1.0」を参照してください。
    guard let data = try? IntdashData.DataInt(id: "intdash-data-example-id", data: Int64(value)) else { return }
    self.sendData(data: data, streamId: streamId, timestamp: timestamp)
}

func sendData(data: IntdashData, streamId: Int, timestamp: TimeInterval) {
    guard let intdash = self.intdash else { return }
    let elapsedTime = timestamp - self.baseTime
    guard elapsedTime >= 0 else { return }
    DispatchQueue.global().async {
        do {
            // 13. iOSデバイス内にデータを保存する場合は `IntdashDataFileManager.write()` でデータを保存する。
            if let fileManager = self.intdashDataFileManager {
                if let fileManager = self.intdashDataFileManager {
                    _ = try fileManager.write(units: [data], elapsedTime: elapsedTime)
                }
            }
            // 14. 生成したデータを`IntdashClient.UpstreamManager.sendUnit()` で送信する。
            try intdash.upstreamManager.sendUnit(data, elapsedTime: elapsedTime, streamId: streamId)
        } catch {
            print("Failed to send data. \(error.localizedDescription)")
            return
        }
    }
}

アップストリーム(計測)を終了する。

16. iOSデバイスにデータを保存している場合は、計測時間を IntdashDataFileManager.setDuration() で保存する。
17. 計測の終了を示すデータを送信する。
18. アップストリームを閉じる。
19. 閉じられているアップストリームを削除する。
20. intdashサーバーとの接続を終了する。(※終了する場合)

func stopUpstream() {
    guard let intdash = self.intdash else { return }
    self.intdash = nil
    // 16. iOSデバイスにデータを保存している場合は、計測時間を `IntdashDataFileManager.setDuration()` で保存する。
    let now = Date().timeIntervalSince1970 // 時間管理をDate()で行っている場合
    let duration = now-self.baseTime
    try? self.intdashDataFileManager?.setDuration(duration: duration)
    self.intdashDataFileManager = nil

    let group = DispatchGroup()
    if let streamId = self.upstreamId {
        self.upstreamId = nil
        do {
            // 17. 計測の終了を示すデータを送信する。
            try intdash.upstreamManager.sendLastData(streamId: streamId)
        } catch {
            print("Failed to send last data. \(error.localizedDescription)")
        }

        if let streamIds = self.upstreamIds {
            self.upstreamIds = nil
            group.enter()
            DispatchQueue.global().async {
                // 18. アップストリームを閉じる。
                intdash.upstreamManager.close(streamIds: streamIds) { (error) in
                    if let error = error {
                        print("Failed to close upstream. \(error.localizedDescription)")
                    } else {
                        print("Success to close upstream.")
                    }
                    // 19. 閉じられているアップストリームを削除する。
                    intdash.upstreamManager.removeClosedUpstream()
                    group.leave()
                }
            }
        }
    }

    group.notify(queue: .global()) {
        // 20. intdashサーバーとの接続を終了する。(※終了する場合)
        intdash.disconnect { (error) in
            if let error = error {
                print("Failed to disconnect to the intdash server. \(error.localizedDescription)")
            } else {
                print("Success to disconnect to the intdash server.")
            }
        }
    }
}

■ 使用している外部ライブラリ