Grow up

生活とプログラミング

iOS Core Bluetooth "Writing is not permitted"の解決方法

はじめに

先週初めて Swift を使ってマイコンと通信をしてみました。
knkomko.hatenablog.com

データは送れてもエラーが返ってきて苦労したので書き残しておきます。

エラー内容

マイコンにデータを送ると以下のエラー内容が返ってきます。

Errpr Domain=CBATTErrorDomain Code=3 "Writing is not permitted."
UserInfo={NSLocalizedDescription=Writing is not permitted.}

以下のデリゲートメソッドがエラーを受信していました。
正常であればNotificationが返ってきます。

    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            println("error \(error)")
            return
        }

        println("received Notification")
    }
解決方法

WriteValueの3番目のパラメータをキャラクタリスティックの仕様と合わせます。
指定できるパラメータは「.withoutResponse」「.withResponse」2種類です。
そのため仕様が分からない場合はどちらかに変更して試します。

私が使用したマイコンの取説には明記されていなかったので「.withoutResponse」に変更しました。

    func  sendMessage(message: String) {
        var command = message + "\n"
        let data = command.data(using: String.Encoding.utf8, allowLossyConversion:true)
        // type:を下記に変更 cbPeripheral!.writeValue(data! , for: writeCharacteristic!, type: .withResponse)
        cbPeripheral!.writeValue(data! , for: writeCharacteristic!, type: .withoutResponse)
    }
感想

"Writing is not permitted."というエラーメッセージを読んでから書き込みの種類を「withoutReponse」に変更しようという考えに至るまでに時間がかかりました。
ドキュメントの概要を確認すると、詳細を理解するためにはBluetooth 4.0の仕様を理解する必要がありそうでした。

以下、概要を抜粋した内容です。

原文:

Characteristic write types have corresponding restrictions on the length of the data that you can write to a characteristic’s value.

日本語訳:

特性書き込みタイプには、特性の値に書き込むことができるデータの長さに対する対応する制限があります。書き込みタイプについては、Bluetooth 4.0仕様の第3部、パートG、セクション4.9.3〜4に制限が定義されています。

developer.apple.com

Bluetooth 4.0 のセントラルからペリフェラルへの書き込みの種類について調べてみたものの、仕様についてはよく分かりませんでした。
仕様を理解できればエラー原因の特定が早くなると思うのですが、現状はまだ手探りの状態です。

iOS Core Bluetooth (Swift) を使用してみた

はじめに

今回はCore Boluetoothを使用してマイコンに文字列を送信してみました。
用語の意味を理解する事に苦労したので概要に意味をまとめています。

Core Bluetooth の概要

クライアント/サーバの考え方が基盤になっています。

ペリフェラル : サービスを提供 (サーバ)
セントラル  : サービスを利用 (クライアント)

f:id:knkomko:20190716002250p:plain:w350


ペリフェラルは見つけてもらう為にアドバタイズパケットと呼ばれる自身の情報を発信しています。
セントラルはアドバタイズパケットを探して周囲のペリフェラルを見つけます。

f:id:knkomko:20190716003642p:plain:w350


接続に成功したペリフェラルと通信する際は、キャラクタリスティックというオブジェクトを使用します。
例えばペリフェラルが天気のサービスを提供するとすれば、気温や湿度というキャラクタリスティックがあります。

f:id:knkomko:20190716010259p:plain:w350

Core Bluetooth のコード

CoreBluetooth をインポートします。使用するクラスには CBCentralManagerDelegate, CBPeripheralDelegate が必要です。
centralManagerDidUpdateState メソッドが無いとビルドエラーになります。

import CoreBluetooth

class bluetoothService: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
   
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch (central.state) {
        case .PoweredOff:
            // BLE PoweredOff
        case .PoweredOn:
            // BLE PoweredOn
        case .Resetting:
            // BLE Resetting
        case .Unauthorized:
            // BLE Unauthorized
        case .Unknown:
            // BLE Unknown
        case .Unsupported:
            // BLE Unsupported
        }
    }
 …
}


周囲のペリフェラルを見つけるためにCBCentralManagerを使用します。

    var centralManager: CBCentralManager?

    override init () {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: nil)
    }


scanForPeripheralsメソッドの引数withSerivicesにnilを渡すと全てのペリフェラルを探してくれます。nilの代わりにサービスのUUIDを渡すとUUIDが一致するペリフェラルを探します。

    func scanStart() {
        if manager!.isScanning == false {
            // サービスのUUIDを指定しない
            centralManager!.scanForPeripherals(withServices: nil, options: nil)
            
            // サービスのUUIDを指定する
            // let service: CBUUID = CBUUID(string: "サービスのUUID")
            // centralManager!.scanForPeripherals(withServices: service, options: nil)
        }
    }


ペリフェラルが見つかると centralManager のデリゲートメソッドが呼び出されます。今回は見つけたペリフェラルは配列に保存して、後で目的のペリフェラルを探して接続するようにしました。

    var peripherals: [CBPeripheral] = []

     func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        peripherals.append(peripheral)
    }


配列から目的のペリフェラルをデバイス名で探して connectメソッドで接続しています。データを送信する際に CBPeripheral が必要になるので目的のペリフェラルはメンバ変数に保持します。

    var cbPeripheral: CBPeripheral? = nil    

    func connect() {
        for peripheral in peripherals {
            if peripheral.name != nil && peripheral.name == "デバイス名" {
                cbPeripheral = peripheral
                manager?.stopScan()
                break;
            }
        }
        
        if cbPeripheral != nil {
            manager!.connect(cbPeripheral!, options: nil)
        }
    }


第二引数が違う centralManager デリゲートメソッドが接続の可否によって呼び出されます。接続が成功した場合には、通信に使うサービスをdiscoverServicesメソッドで探します。

    // 接続が成功すると呼ばれるデリゲートメソッド
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        cbPeripheral?.delegate = self
        
        // 指定のサービスを探す
        let services: [CBUUID] = [CBUUID(string: "サービスのUUID")]
        cbPeripheral!.discoverServices(services)
       
        // すべてのサービスを探す
        // cbPeripheral!.discoverServices(nil)
    }
    
    // 接続が失敗すると呼ばれるデリゲートメソッド
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
       println("connection failed.")
    }


サービスが見つかるとperipheral デリゲートメソッドが呼び出されます。見つかったサービスの中から今度は discoverCharacteristics メソッドを使用してキャラクタリスティックを探します。

    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        let serviceUUID: CBUUID = CBUUID(string: "サービスのUUID")
        for service in cbPeripheral!.services! {
            if(service.uuid == serviceUUID) {
                cbPeripheral?.discoverCharacteristics(nil, for: service)
             }
        }
    }


キャラクタリスティックが見つかると引数が違うperipheralデリゲートメソッドが呼び出されます。
今回使用したマイコンにはNotifyとWriteという2種類のキャラクタリスティックがあったためそれぞれ識別しています。
属性がWriteのキャラクタリスティックを使用して文字列データをマイコンに送信するため、そちらはメンバ変数に保持しています。

    var writeCharacteristic: CBCharacteristic? = nil

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {       
        for characreristic in service.characteristics!{
            if characreristic.uuid.uuidString == "属性がNotifyのキャラクタリスティックのUUID" {
                //Notificationを受け取るハンドラ
                peripheral.setNotifyValue(true, for: i)
            }
            
            if characreristic.uuid.uuidString == "属性がWriteのキャラクタリスティックのUUID" {
                writeCharacteristic = characreristic
            }
        }
    }


蛇足ですが今回使用したマイコンが「ペリフェラルの接続成功」と「Notifyが有効になる」場合に接続が確立されるとのことで setNotifyValue メソッドを実行するとマイコンの接続中LEDが緑色に点灯してくれました。
f:id:knkomko:20190716012054j:plain:w150


属性がWriteのキャラクタリスティックを使用して文字列データの送信を行います。
writeValue メソッドを使用することで UTF8 に変更した文字列データをマイコンに送信しています。

    func  sendMessage(message: String) {
        var command = message + "\n"
        let data = command.data(using: String.Encoding.utf8, allowLossyConversion:true)
        cbPeripheral!.writeValue(data! , for: writeCharacteristic!, type: .withoutResponse)
    }


ペリフェラルが文字列データの受信に成功するとNotificationが返ってきます。
そのNotificationを検出すると引数が違う peripheral デリゲートメソッドが呼び出されます。

    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            println("error \(error)")
            return
        }

        println("received Notification")
    }


Core Bluetooth を使用する中で受信を行う場合には以下のデリゲートメソッドを使用します。

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        let notify: CBUUID = CBUUID(string: "ペリフェラルから値が受信できるキャラクタリスティックのUUID")
        if characteristic.uuid.uuidString == notify.uuidString {
            let message = String(bytes: characteristic.value!, encoding: String.Encoding.ascii)
            println("received \(message)")
        }
    }

Xcode Could not locate device support files の解決方法

はじめに

古いiPadを実機デバッグで使ったらXcodeが対応してないとエラーになりました。
iPadのOSに対応したサポートファイルが必要だったため残しておきます。

f:id:knkomko:20190714131225p:plain:w350

エラー内容

Could not locate device support files.
This iPad(4th generation Model A1458) is running iOS 10.3.3 (14G60).
which may not be supported by this version of Xcode.

サポートファイルを確認

以下の記事を参考にしました。
【Swift】エラー「Could not locate device support files(iOS 12.2)」の対処方法|ぴっぴproject

確認してみると既にiOS 10.3.3 (14G60) がありました。
f:id:knkomko:20190714131256p:plain:w350

サポートファイルはiPadMacに繋げて実機デバッグした時に作られたようです。

解決方法

初期のマイナーバージョンのサポートファイルを入れたら解決できました。

今回の場合は10.3のサポートファイルです。
f:id:knkomko:20190714125306p:plain:w350

サポートファイルはGithubからダウンロードします。
iOS-DeviceSupport/DeviceSupport at master · iGhibli/iOS-DeviceSupport · GitHub

ダウンロードしたサポートファイルを iOS DeviceSupport フォルダに入れます。
場所は、ライブラリ → Developer → XcodeiOS DeviceSupport

Xcode を終了してから起動した後に実機デバッグが出来ました。

ライブラリの表示方法

Mac初心者な私はライブラリのフォルダが見つけられませんでした。

移動メニューを表示した状態で、optionキーを押すとライブラリが表示されます。
f:id:knkomko:20190714130415p:plain:w350

optionキーを離すとライブラリの表示が消えます。

Xcode Underlying error code 1100 を解決する方法

はじめに

久々にXcodeを起動したらビルドエラーになったので忘備録としてまとめます。

エラー内容

2つ表示されていました。

The operation couldn't be compleated. Unable to log in with account 'Apple ID'.
An unexpected failure occurred while logging in (Underlying error code 1100).

No profiles for 'Project name' where found:Xcode couldn't find a provisioning profile matching 'Project name'.

解決方法

Xcode の開発アカウントを再度ログインすることで解決できました。
まず Xcode から Preferences を左クリック。
f:id:knkomko:20190713231156p:plain:w350

エラーの記載の中にログインボタンのようなものがあるので左クリック。
スクリーンショットが無いのですが、画面赤色部分に表示されていました。
f:id:knkomko:20190713232153p:plain:w350

ログインが終了したら、再度ビルドしてエラーにならない事を確認して終了です。

参考資料

stackoverflow.com

C# .Net Core でファイルをWebからダウンロードする方法

はじめに

今回はファイルをダウンロードする方法についてまとめます。
file.zip というファイルをダウンロード出来るようにしました。

開発環境

・Windows10 Pro
・VisualStudio2019
Visual Studio のインストール | Microsoft Docs

動作

画面上のダウンロードボタンを左クリックします。
f:id:knkomko:20190710082415p:plain:w350

ファイルがダウンロードできます。
f:id:knkomko:20190710082634p:plain:w350

ダウンロードしたファイルは、ダウンロードフォルダに保存されます。
f:id:knkomko:20190710083221p:plain:w350

1. ASP.Net Core MVC アプリケーションを作成する

ASP.NET Core Webアプリケーションを選択して、次へ。
f:id:knkomko:20190710081128p:plain:w350

Web アプリケーション(モデル ビュー コントローラー)を選択して、作成。
f:id:knkomko:20190710081201p:plain:w350

2. フォルダの作成とファイルの追加

Downloadフォルダをプロジェクトの下に作成します。
Downloadフォルダに file.zip のファイルを作成します。
f:id:knkomko:20190710123936p:plain:w350

3. ボタンの追加

f:id:knkomko:20190710124359p:plain:w350

Index.cshtml にダウンロードボタンを追加します。

<button class="btnDownload" type="button">ダウンロード</button>
4. ボタン押下時のアクションを追加 1/2

f:id:knkomko:20190710123908p:plain:w350

HomeController.cs に Download メソッドを追加します。

[Route("Download")]
public ActionResult Download()
{
    var filePath = Path.Combine(Directory.GetCurrentDirectory(), "Download", "file.zip");
    var file = System.IO.File.ReadAllBytes(filePath);
    return File(file, MediaTypeNames.Application.Zip, "file.zip");
}
5. ボタン押下時のアクションを追加 2/2

js フォルダに download.js ファイルを作成します。
f:id:knkomko:20190711123023p:plain:w350

download.js にボタン押下時 Download メソッドを呼び出す処理を追加します。
注意点として、変数 mySiteUrl のポート番号が実行した時のポート番号と違う場合には変更してください。

$('.btnDownload').click(function () {
    var documentUrl = document.URL;
    var baseUrl = documentUrl.substring(0, documentUrl.indexOf('currentPageName'));
    var mySiteUrl = 'https://localhost:44369/'
    location.href = mySiteUrl + '/Download'
});
6. jsファイルを読み込む一文を追加

f:id:knkomko:20190711124210p:plain:w350

_Layout.cshtml に download.js を読み込む一文を追加します。
これがないとダウンロード時に Uncaught ReferenceError: $ is not defined とエラーになります。

<script src="~/js/download.js"></script>

stackoverflow.com

7. 動作確認

ファイルのダウンロードが出来ることを確認して終了です。
f:id:knkomko:20190710083221p:plain:w350

C# で S3 から圧縮ファイルをダウンロードする方法

はじめに

S3 から圧縮ファイルをダウンロードする方法についてまとめます。
今回はWindowsFormアプリケーションを使用しました。
f:id:knkomko:20190630021705p:plain:w450

S3に sample.zip を準備してダウンロードしました。
f:id:knkomko:20190630021909p:plain:w350

ソースコード
using System;
using System.Windows.Forms;

using Amazon.S3;
using Amazon.S3.Transfer;

namespace WindowsFormsApp3
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public void DownloadS3Object(string awsBucketName, string keyName)
        {
            AmazonS3Config amazonS3Config = new AmazonS3Config()
            {
                ServiceURL = "https://s3.amazonaws.com"
            };

            AmazonS3Client s3Client = new AmazonS3Client("awsAccessKeyId",
                                                         "awsSecretAccessKey",
                                                         amazonS3Config);

            using (TransferUtility fileTransferUtility = new TransferUtility(s3Client))
            {
                fileTransferUtility.Download(@"C:\temp\" + keyName, awsBucketName, keyName);
            }
        }

        private void Download_Click(object sender, EventArgs e)
        {
            string keyName = "sample.zip";
            DownloadS3Object("awsBucketName", keyName);
        }
    }
}

ダウンロードした圧縮ファイルを展開し、内容も問題ありませんでした。
f:id:knkomko:20190630022112p:plain:w350

不採用のソースコード

GetObjectRequestはダウンロードが出来ても展開はできませんでした。

using System;
using System.Windows.Forms;

using Amazon.S3;
using Amazon.S3.Model;
using System.Runtime.Remoting.MetadataServices;
using System.IO;

namespace WindowsFormsApp3
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public Stream DownloadS3Object(string awsBucketName, string keyName)
        {
            AmazonS3Config amazonS3Config = new AmazonS3Config()
            {
                ServiceURL = "https://s3.amazonaws.com"
            };

            AmazonS3Client s3Client = new AmazonS3Client("awsAccessKeyId",
                                                         "awsSecretAccessKey",
                                                         amazonS3Config);
            using (var client = s3Client)
            {
                Stream stream = new MemoryStream();
                GetObjectRequest request = new GetObjectRequest { BucketName = awsBucketName, Key = keyName };
                using (GetObjectResponse response = client.GetObject(request))
                {
                    response.ResponseStream.CopyTo(stream);
                }
                stream.Position = 0;
                return stream;
            }
        }

        private void Download_Click(object sender, EventArgs e)
        {
            string keyName = "sample.zip";
            Stream stream = DownloadS3Object("awsBucketName", keyName);
            MetaData.SaveStreamToFile(stream, @"C:\temp\" + keyName);
        }
    }
}

ダウンロードした圧縮ファイルを展開しようとするとエラーが発生します。
f:id:knkomko:20190630021737p:plain:w350

cs が見つかりませんのエラーを解決する方法

はじめに

AWS S3 を使うために PutObjectRequest を実行すると cs ファイルが見つからないとエラーになりました。
VisualStudio のデバッグ設定を変更したら解消できたのでまとめます。

エラー内容

PutObjectRequest を実行すると発生します。
f:id:knkomko:20190629232021p:plain:w350

解決策

ツール(T) → オプション(O) を左クリックします。
f:id:knkomko:20190629225323p:plain:w350

デバッグ → 全般 にある「マイコードのみを有効にする」をチェックしてからOKボタンを左クリックして設定を保存します。
f:id:knkomko:20190629225334p:plain:w350

再度プロジェクトを実行して、エラーが発生しないことを確認します。

参考サイト

social.msdn.microsoft.com