BlankTar

about | blog | works | photo

すこし前にsystemdに乗り換えたのですが、そのせいで前に設定したジャンボフレームが無効になってしまっていました。
特段困りもしないので放置していたのですが、なんとなく設定しなおしてみました。

/etc/systemd/network/eth0.networkを開いて、以下のような記述を追加します。

[Link]
MTUBytes=6122

で、再起動すれば設定が反映されているはず。簡単ですね。

ちなみに、/usr/lib/systemd/network/99-default.linkを書き換えればデフォルトの設定を変更出来るようです。そっちでも良いかも。

参考: systemd-networkd - ArchWiki

アプリを作っています。Eddystone-URLを受信するアプリです。
iOSだとどうにも情報が無くて。そしてswiftはバージョンにころころ変わるらしく。ちくしょうという感じです。

swiftは何にも分からないので、あまりエレガントなコードではないと思いますが、よしなに。

検証した端末はiPhone 6S、iOS 10.2.1(14D27)。Base SDKはiOS 10.2です。
swiftのバージョンは3.0.2です。swiftlang-800.0.63 clang-800.0.42.1らしいです。
バージョン問題に悩まされたのでとことん書きます。ちくしょうという感じです。

で、とりあえずメイン部分。ViewController.swiftに書くやつ。

import UIKit
import CoreBluetooth


class ViewController: UIViewController, CBCentralManagerDelegate {
    var centralManager: CBCentralManager!

    override func viewDidLoad() {
        super.viewDidLoad()

        centralManager = CBCentralManager(delegate: self, queue: nil)  // 初期化する。スキャンの開始はcentralManagerDidUpdateStateの中で。
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        centralManager.stopScan()  // 画面から消えたらスキャンを停止する。
    }

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == CBManagerState.poweredOn {  // 超重要。理由は後述。
            print("start scan")

            // スキャン開始
            central.scanForPeripherals(withServices: [CBUUID(string: "FEAA")],
                                       options: [CBCentralManagerScanOptionAllowDuplicatesKey : true])
        } else {
            print("not ready")
        }
    }
    
    @objc(centralManager:didDiscoverPeripheral:advertisementData:RSSI:) func centralManager(_ central: CBCentralManager,
                                                                                            didDiscover peripheral: CBPeripheral,
                        advertisementData: [String : Any],
                        rssi RSSI: NSNumber) {
        
        if let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [NSObject : AnyObject] {
            let data = serviceData[CBUUID(string: "FEAA")]
            
            let es: EddystoneURL  // パースして良い感じにするクラス。自作した。後述。
            do {
                try es = EddystoneURL(RawData: data as! NSData)
            } catch {
                return
            }
            
            print("data: \(es)")
        }
    }
}

超重要ってコメントで書いた部分が超重要です。iOS 8あたりから必要になったらしいです。
ググって出てくるサンプルを見ているとこのif文無しでやっているのですが、そうすると以下のようなエラーが出ます。

[CoreBluetooth] API MISUSE: <CBCentralManager: 0x17426af00> can only accept this command while in the powered on state

CBCentralManagerのstateってやつを確認して、poweredOnであることを確認してからスキャンを開始するようにすれば問題ありません。

で、BLEのペイロード部分のパースは次のクラスで。基本的には公式の仕様に従ってひたすら実装しただけのやつです。

import Foundation

class Eddystone : NSObject {
    static let URLEncodings: [UInt8: String] = [0x00: "http://www.",
                                                0x01: "https://www.",
                                                0x02: "http://",
                                                0x03: "https://"]
    
    static let DomainExpansions: [UInt8: String] = [0x00: ".com/",
                                                    0x01: ".org/",
                                                    0x02: ".edu/",
                                                    0x03: ".net/",
                                                    0x04: ".info/",
                                                    0x05: ".biz/",
                                                    0x06: ".gov/",
                                                    0x07: ".com",
                                                    0x08: ".org",
                                                    0x09: ".edu",
                                                    0x0a: ".net",
                                                    0x0b: ".info",
                                                    0x0c: ".biz",
                                                    0x0d: ".gov"]
    
    var TxPower: Int8
    var url: String
    
    
    init(RawData data: NSData) throws {
        var bytes = [UInt8](repeating: 0, count: data.length)
        data.getBytes(&bytes, length: data.length)
        
        // 1バイト目はFrame Specification。Eddystone-URLしか扱わないので、0x10で固定。
        if bytes[0] != 0x10 {
            throw NSError(domain: "it isn't EddystoneURL", code: -1, userInfo: nil)
        }
        
        // 2バイト目はTx Power Level。とりあえず保存しておく。
        TxPower = Int8(bitPattern: bytes[1])
        
        // 3バイト目はURL Scheme Prefix。
        if let t = Eddystone.URLEncodings[bytes[2]] {
            url = String(t)
        } else {
            throw NSError(domain: "invalid payload", code: -1, userInfo: nil)
        }
        
        // 4バイト目以降はURLのプレフィックスより後ろの部分。
        for i in 3..<data.length {
            // いくつかのドメインは1バイトに省略出来るらしい。
            if let t = Eddystone.DomainExpansions[bytes[i]] {
                url += String(t)
            } else {
                url += String(format: "%C", bytes[i])
            }
        }
    }
    
    override var description: String {
        return String(format: "EddystoneURL(power: %d): " + url, TxPower)
    }
}

愚直に実装しただけって感じです。

この二つのクラスを書いて実行してやると、デバッグコンソールに受信したEddystone URLのTx PowerとURLが表示されるはずです。
ご武運を。うへぇ。

参考:
google/eddystone: Specification for Eddystone, an open beacon format from Google
Google発のBeacon用オープンフォーマットEddystoneをAndroidで触ってみた - Qiita
Core Bluetooth and the Beacon Manager App | Iot Design Shop - Developer Portal
Eddystone と iOS - その2: 実装編 - Qiita
Core Bluetooth with Swift (ObjCのおまけ付き) - Qiita

Xamarinです。Xamarin.Formsです。
画像があって、画像の上に文字を重ねる必要があったので、試してみました。
ここではImageとLabelの組み合せでやっていますが、多分他の物でも出来ると思います。

といっても方法はかなり単純で、Gridレイアウトに場所を指定せずに書くだけです。
XAMLで書くと以下のような感じ。

<Grid>
	<Image x:Name="image" />
	<Label Text="Hello World" VerticalOptions="Center" HorizontalOptions="Center" />
</Grid>

これでimageの上にHello Worldという文字が重なります。簡単。

C#だけで書く場合は以下のようになります。

Content = new Grid {
	Children = {
		new Image { Source = /* ここは適当に */ },
		new Label { Text = "Hello World", HorizontalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center },
	},
};

やっぱり簡単。

ちなみに、重なりの順序は記述した順番になるようです。
つまり、後にあるものほど上に、始めにあるものほど奥に。

[ << ] [ < ] [ 1 ] [ 3 ] [ >> ]