iOS Core Bluetooth "Writing is not permitted"の解決方法
エラー内容
マイコンにデータを送ると以下のエラー内容が返ってきます。
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に制限が定義されています。
Bluetooth 4.0 のセントラルからペリフェラルへの書き込みの種類について調べてみたものの、仕様についてはよく分かりませんでした。
仕様を理解できればエラー原因の特定が早くなると思うのですが、現状はまだ手探りの状態です。
iOS Core Bluetooth (Swift) を使用してみた
はじめに
今回はCore Boluetoothを使用してマイコンに文字列を送信してみました。
用語の意味を理解する事に苦労したので概要に意味をまとめています。
Core Bluetooth の概要
クライアント/サーバの考え方が基盤になっています。
ペリフェラル : サービスを提供 (サーバ)
セントラル : サービスを利用 (クライアント)
ペリフェラルは見つけてもらう為にアドバタイズパケットと呼ばれる自身の情報を発信しています。
セントラルはアドバタイズパケットを探して周囲のペリフェラルを見つけます。
接続に成功したペリフェラルと通信する際は、キャラクタリスティックというオブジェクトを使用します。
例えばペリフェラルが天気のサービスを提供するとすれば、気温や湿度というキャラクタリスティックがあります。
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が緑色に点灯してくれました。
属性が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)") } }
参考資料
iOSでのBluetooth通信入門 :CoreBluetooth プログラミングガイド 解説 : Core Bluetoothの概要 | IoT
Working with Core Bluetooth in iOS 11 | Swift Tutorial
Swift – Bluetooth Low Energy communication using Flow Controllers – Wojciech Kulik
iOS SwiftでBLEのサンプルを動かしてみる - Qiita
CoreBluetoothでNotificationを受け取る! – 野生のプログラマZ
Core Bluetooth with Swift (ObjCのおまけ付き) - Qiita
CoreBluetoothでiPhoneとMacをBLE接続してみた。 - Qiita
RaspberryPi3 を Bluetooth(Peripheral) にして iPhone と通信してみた - Qiita
Xcode Could not locate device support files の解決方法
エラー内容
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) がありました。
解決方法
初期のマイナーバージョンのサポートファイルを入れたら解決できました。
今回の場合は10.3のサポートファイルです。
サポートファイルはGithubからダウンロードします。
iOS-DeviceSupport/DeviceSupport at master · iGhibli/iOS-DeviceSupport · GitHub
ダウンロードしたサポートファイルを iOS DeviceSupport フォルダに入れます。
場所は、ライブラリ → Developer → Xcode → iOS DeviceSupport
ライブラリの表示方法
Mac初心者な私はライブラリのフォルダが見つけられませんでした。
移動メニューを表示した状態で、optionキーを押すとライブラリが表示されます。
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 を左クリック。
エラーの記載の中にログインボタンのようなものがあるので左クリック。
スクリーンショットが無いのですが、画面赤色部分に表示されていました。
ログインが終了したら、再度ビルドしてエラーにならない事を確認して終了です。
参考資料
C# .Net Core でファイルをWebからダウンロードする方法
はじめに
今回はファイルをダウンロードする方法についてまとめます。
file.zip というファイルをダウンロード出来るようにしました。
開発環境
・Windows10 Pro
・VisualStudio2019
Visual Studio のインストール | Microsoft Docs
動作
画面上のダウンロードボタンを左クリックします。
ファイルがダウンロードできます。
ダウンロードしたファイルは、ダウンロードフォルダに保存されます。
1. ASP.Net Core MVC アプリケーションを作成する
ASP.NET Core Webアプリケーションを選択して、次へ。
Web アプリケーション(モデル ビュー コントローラー)を選択して、作成。
2. フォルダの作成とファイルの追加
Downloadフォルダをプロジェクトの下に作成します。
Downloadフォルダに file.zip のファイルを作成します。
3. ボタンの追加
Index.cshtml にダウンロードボタンを追加します。
<button class="btnDownload" type="button">ダウンロード</button>
4. ボタン押下時のアクションを追加 1/2
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 ファイルを作成します。
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ファイルを読み込む一文を追加
_Layout.cshtml に download.js を読み込む一文を追加します。
これがないとダウンロード時に Uncaught ReferenceError: $ is not defined とエラーになります。
<script src="~/js/download.js"></script>
7. 動作確認
ファイルのダウンロードが出来ることを確認して終了です。
C# で S3 から圧縮ファイルをダウンロードする方法
はじめに
S3 から圧縮ファイルをダウンロードする方法についてまとめます。
今回はWindowsFormアプリケーションを使用しました。
S3に sample.zip を準備してダウンロードしました。
必要なもの
・Visual Studio 2017
Visual Studio のインストール | Microsoft Docs
・AWSSDK.S3
https://www.nuget.org/packages/AWSSDK.S3/
ソースコード
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); } } }
ダウンロードした圧縮ファイルを展開し、内容も問題ありませんでした。
不採用のソースコード
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); } } }
ダウンロードした圧縮ファイルを展開しようとするとエラーが発生します。