読者です 読者をやめる 読者になる 読者になる

もっふもふの電子回路

備忘録的なあれ

Visual Studio 2017 + Xamarinでクロスプラットフォーム開発(1)

背景

クロスプラットフォームアプリを作りたくて環境構築にひたすら手間取った話。

環境:
・windows10 Pro
Visual Studio 2017
・Xamarin 4.4
mac Sierra 10.12.4
Xcode 8.3.2
・Xamarin Studio 6.3(build 863) <-成功
Visual Studio for Mac <-失敗

Windowsメインで、macエミュレーターを起動させました。

ゴール

前回の環境でWindowsのVisual Studio2017にてテストアプリビルドによるAndroidエミュレータ起動まで確認できました。
今回はiOSエミュ起動がゴールです。

発生した問題

Visual StudioのXamarinクロスプラットフォーム アプリテンプレートのバグ

mac OSおよびXcodeを最新にし、Visual Studio for Macをインストール
Visual Studio for Macにてテストアプリがビルド&iOSエミュが立ち上がることを確認し、今度はWindows側でテストアプリにてビルドできるか確認。
しかしビルド時にいくつかの画像ファイルが読み込めないトラブルが発生した。
プロジェクトディレクトリ内のアプリ名.iOS/Resources 内にあるテンプレートからコピーされるはずの画像類、例えば「Default-568h@2x.png」など。
エラーメッセージから該当パスを確認するとブランク(真っ黒)になった画像ファイルが見つかった。

理由は不明だが、テンプレートからコピーする際にバイナリが壊れる模様。何度か試しても同様の結果になった。ちなみにテンプレートの大本の画像は無事だった。

仕方なくテンプレートをXamarin関係の別のものを使うことにし、人気順で一番上にきた「Prism Template Pack」を使用。
Prism Template Pack - Visual Studio Marketplace

インストール後に新規追加されたテンプレートを試すと上記問題は解消した。
f:id:gadgetcat:20170422192454p:plain


Xamarinのテンプレートは試験運用中ということで覚悟はしていたがこんな問題が起こるとは想定外だった。
ちなみにAutofacとかDrylocとかなにそれ怖い知らない。これも覚えた方がいいのかなぁ

iOS.exe conflicts with another assembly

続いてビルドしたところ、アプリ名+iOS.exeが競合しているというエラーが出現。
エラーメッセージで調べてもごく最近起こっている問題のようで、明確な解決策を見つけることができなかった。
もちろんMac側のビルドキャッシュを削除したり、該当ディレクトリに異常はないかはチェックした。

途方にくれていたがふとVisual Studio for Macはプレビュー版だったことを思い出し、いったんアンインストール
Xamarin Studioを入れた。
インストール後すぐにXamarin Studioの更新通知が。Xamarinで使うSDKを更新するぜ!という内容だったが、これが効いたのか無事Visual Studio 2017側からビルド成功、エミュレータが立ち上がることを確認した。


上記問題等により1日潰れてしまったが、エミュレータが立ち上がった時は震えた。
(そもそもMacで開発すればいいのでは?と思われるかもしれないが、自分は生粋のwindowerであるため却下)

次はIsolatedStorageあたりを使ってアプリ領域内にストレージを確保する処理を作りたい。
ちなみに自分はWPFアプリは開発経験があるが、iOSAndroidアプリはさっぱりなので大変苦労すると思う。

以上

Twitter APIで連打でリクエスト投げたときの話

背景

環境:
wordpress
・x-server
・PHP5.6.22くらい
・ライブラリ TwistOAuth(非phar)

Twitter APIにてsearchリクエストを連打で投げる必要がある案件にあたったけど、リクエスト上限(180req/15min)にすぐ引っかかるような要求仕様だったので苦労した話

TwistOAuth

すげーいい。これがあればすぐプログラム組める。使いやすい。
でもリクエスト上限に達した場合に例外吐くだけなのでリスポーン処理が組みづらい。

対策

TwistOAuth内のTwistOAuth.phpでgetリクエストする際のcurlのオプションと、関数decodeを修正した。

public function get
public function get($url, $params = array(), $proxy = '') {
    $ch = $this->curlGet($url, $params, $proxy);
    curl_setopt($ch, CURLOPT_HEADER, true);//--追記
    $response = curl_exec($ch);
    return self::decode($ch, $response);
}
private static function decode
private static function decode($ch, $response) {
    self::checkCurlError($ch);
    $info = curl_getinfo($ch);
    $header = substr ($response, 0, $info["header_size"]);//--追記
    $body = substr ($response, $info["header_size"]);//--追記
    $response = $body;//--追記

||途中省略||

$limitRemain = -1;//残りリクエスト可能回数
$limitReset = -1;//リクエスト回数復活時間unixtime
$headerArr = explode("\n", $header);
$headerArr = array_map('trim', $headerArr);
foreach($headerArr as $headerRow){
    $headerRowData = explode(":", $headerRow);
    if(is_array($headerRowData) && count($headerRowData)==2){
        if($headerRowData[0]=="x-rate-limit-remaining"){
            //echo "x-rate-limit-remaining --- ".$headerRowData[1]."<BR>";
	    if(is_numeric($headerRowData[1])){
	        $limitRemain = intval($headerRowData[1],10);
            }
        }else if($headerRowData[0]=="x-rate-limit-reset"){
            //echo "x-rate-limit-reset --- ".$headerRowData[1]."<BR>";
            if(is_numeric($headerRowData[1])){
                $limitReset = intval($headerRowData[1],10);
            }
        }
    }
}
if($limitRemain!=-1 && $limitRemain===0 && $limitReset!=-1){
    $nowTime = time();
    $waittime = $limitReset-time()+60;
    if($waittime&lt;=0){$waittime=960;}
    update_option('waittime', $waittime);
}else{
    update_option('waittime', 0);
}
return $obj;

function getにレスポンスヘッダを返すためにCURLOPT_HEADERをtrueに。
function decodeの頭でヘッダとレスポンスbodyを分離。
function decodeで正常パターンだった場合のreturn $obj前に残りリクエスト回数解析処理を追加。

意図としては、
Twitter APIでは現在のリクエスト可能回数を取得するエンドポイントがあるが、いちいち確認してたら無駄にリソースを食ってしまう。
そこで、get時に返ってくるレスポンスヘッダを見るようにし、レスポンスヘッダ内の値
・x-rate-limit-remaining (残りリクエスト可能回数)
・x-rate-limit-reset (リクエスト可能回数リセット時間:unixtime)
を毎回確認するようにした。

$waittime = $limitReset-time()+60;
にてリセットされる時刻-現在時刻にて待ち時間を作成。+60は気分で入れた。

環境はwordpressなので、サイトオプションに"waittime"を作成。残りリクエスト可能回数が0以下だった場合に
update_option('waittime', $waittime)
にて次回復活時間をオプションに登録、プログラム上位にてこの値を確認するようにした。


上位でこの"waittime"を都度確認して、0以外だったら

wp_clear_scheduled_hook( 'イベントフック名' );
wp_schedule_event( time()+$waittime, '時間設定名', 'イベントフック名' );
update_option('waittime', 0);

とかやればいい感じにリクエスト回数復活まで待ってくれるはず。
コードが雑なのは勘弁してください。


以上





(はてぶコード内で<をエスケープするにはどうすればええんや...)

XamarinとAndroid Studioの共存

背景

環境:
Windows 10
Visual Studio Community 2017

Xamarinでアプリを作ろうとしてインストールしたけどベースにしたいアプリがAndroid Studioで組まれたものだったので、とりあえずXamarin入れた後にAndroid Studio入れて、SDKはXamarinで入れたものを使おうとしたらパスにスペースが入ってたらあかんで!って言われてどうにかSDKを共存させようとした話。
SDKサイズ大きいのでね、節約したいですよね。

Xamarin側のSDKパスを変更

ツール->オプション->Xamarin->Androidの設定
よりパスをAndroid Studioで入れたものに変更
デフォルトだと C:\Users\xxxx\AppData\Local\Android\sdk

SDK ManagerのGUIが上がらない

上記パスに変更したらGUIが立ち上がらなくなった。
いろいろ調べても解決しなかったので途方に暮れていたらSDKディレクトリ内のSDK Readme.txtに書いてあった。

解決

To start the SDK Manager, please execute the program "SDK Manager.exe".

From the command-line you can also directly trigger an update by
executing:
  tools\android.bat update sdk --no-ui

…つまりあれか、Android Studioで入れたやつは勝手に"--no-ui"オプションをつけて立ち上げているのでは?と思い試しにXamarinで入れたSDKディレクトリ内のandroid.batをAndroid Studioで入れたSDK内に上書きした。
(.batの差分とったけどそれっぽい違いがあった。Android Studioから起動するSDK Managerは専用のGUIっぽいしね。)

f:id:gadgetcat:20170421121403p:plain
立ち上がったやんけ!

SDKAndroid Studioですでに入れたものがちゃんと出ている。

あーでも、Android StudioSDK Manager上げたらおかしくなるんじゃないの?と思って立ち上げてみたけどぱっと見問題なし。



とりあえずこれで無駄にディスク容量食わずに開発できそうです。
ただNDKはXamarinとAndroid Studioで要求バージョン違うので、とりあえず別個に入れてます。(あとで統一するかも)

注意点として

・XamarinでインストールしたSDK Managerは直接削除せずに「プログラムと機能」から削除すべき
Visual StudioインストーラーにてXamarinが入った状態で「変更」をクリックすると勝手にSDKが復活するので押さないこと(なんだこのクソ仕様)
(追記)
Android SDKディレクトリ内のadb.exeがVisual Studioを終了しても落ちない。VS落とした後に再度Androidをビルドしようとするとうまくいかなくなるかもしれないので都度落とした方がいいかも

以上

wordpress rss feed 設定メモ

今までrssフィードをページに表示する際にはページのテンプレートに表示処理を書いて、
キャッシュの更新間隔等の設定は「wp-includes\feed.php」内のset_cache_durationを直接変更していた。

これではwordpressを更新した際に変更した設定がデフォルトに戻されるので、
今までは顧客納品時にwordpressの更新はしないでくださいなどと言っていた。
しかし普通に解決方法があったので懺悔がてらメモに記載。

今までやっていた記載。

テンプレートファイル

//WrodPressのfeed.phpの呼び出し
include_once ABSPATH . WPINC . '/feed.php';
// 目的のFeedを取得
$feed = fetch_feed('rssのurl');

//以下 $feed内のメンバ関数にて表示処理

wp-includes\feed.php

function fetch_feed( $url ) {
・
・
・
//$feed->set_cache_duration( apply_filters( 'wp_feed_cache_transient_lifetime', 12 * HOUR_IN_SECONDS, $url ) );
$feed->set_cache_duration( apply_filters( 'wp_feed_cache_transient_lifetime', 30 * MINUTE_IN_SECONDS, $url ) ); //更新間隔を30分ごとに変更
}

解決方法

テンプレートファイル

//WrodPressのfeed.phpの呼び出し
include_once ABSPATH . WPINC . '/feed.php';
// 目的のFeedを取得
$feed = fetch_feed('rssのurl');
$feed ->set_cache_duration(1800);
$feed ->init();

//以下 $feed内のメンバ関数にて表示処理

上記のように普通にインスタンスからset_cache_duration設定できたっぽい。知らなかった。
今まですごい頭悪いことしてたんだな。。。お客様今まで大変申し訳ございませんでした。

以上

Puredata(Pd-extended) + Raspberry Pi (1)

Raspberry Piにpuredata(pd-extended)をインストールします。

Raspbian Jessieでは一工夫必要なので参考になればと

H/W、S/W構成

・Raspberry Pi 2 Model B
OS:Raspbian Jessie

準備

まずはリポジトリ取得先編集

$ sudo vi /etc/apt/sources.list
deb-src http://archive.raspbian.org/raspbian/ jessie main contrib non-free rpi

上記をコメントインして更新

$ sudo apt-get update 

先に必要になるパッケージを入れておきます。

$ sudo apt-get install ttf-dejavu-core libfftw3-3 libftgl2 libgsl0ldbl  liblua5.1-0 libquicktime2 

インストール

そして次はPd-extendedの取得です。
とりあえずゴミ掃除が面倒なのでtmpディレクトリに移動してからパッケージを取ってきます。そしてインストール。

$ cd /tmp/
$ wget https://puredata.info/downloads/pd-extended-0-43-3-on-raspberry-pi-raspbian-wheezy-armhf/releases/1.0/Pd-0.43.3-extended-20121004.deb
$ sudo dpkg -i Pd-0.43.3-extended-20121004.deb 

インストールできたらあとはパーミッションを変更しておきます。

$ sudo chmod 4755 /usr/bin/pd-extended

そして、Raspbian JessieではAlsa関連の設定ファイルの場所がいろいろ変っているのですが、どうもPd-extendedは以前のAlsaの設定ファイルを読みに行っているような感触なのでAlsaの設定変更が効かなかったりと面倒なことになります。
ですので、新しいAlsa設定ファイルを旧設定ファイルの位置にコピーしておきます。

$ sudo cp /usr/share/alsa/alsa.conf /etc/modprobe.d/alsa-base.conf

/usr/share/内のファイルが新しいAlsaのファイル類で、
これを/etc/modprobe.d/にコピーしておきます。
/etc/modprobe.d/が昔のAlsa設定ファイルのあった場所です。
名前も昔のものに変えておきます。

GUI環境でターミナルよりpd-extendedと叩けば立ち上がります。
Raspberry Piのデフォルト環境ではPdのウインドウに「Alsa input error」なる表示が出ておりびっくりするかと思いますが、そもそもデフォルトだと音声入力機器がないので仕方がないエラーです。
気になる場合はとりあえずPdのオーディオ設定から入力デバイスのチェックを外すとエラーが出なくなると思います。

次はUSBオーディオインターフェースを装着・設定してPd上にてモニタリングを行います。

iPhone5 + Bluetooth + Raspberry Pi

前回Raspberry Pi(以下RPi)にBluetoothドングルを装着して周囲のデバイスをスキャンするところまで行いました。
Raspberry Pi + Bluetooth - もっふもふの電子回路
今回はiPhoneとのコネクション確立を行います。

H/W、S/W構成

・Raspberry Pi 2 Model B
OS:Raspbian Jessie
Bluetooth ドングル Princeton PTM-UBT7
iPhone 5 iOS9.2

結論

先に結論から言いますと、コネクション(ペアリング)は確立できました。
しかし、実現させたかった動作は、
iPhoneからRPiを検索、検出、ペアリングの動作であり、それはまだ未達成です。

今回はRPiからiPhoneに対しペアリングを行う方法を記載します。

前回の残課題

前回RPiにて

sudo hcitool lescan

BLEデバイスの検出を試みましたが何も見つかりませんでした。
iPhoneくらいは引っかかるだろうと思っていたので腑に落ちなかったため少しそれについて調べました。
海外サイトを巡ってみるとBeacon Toolkitというアプリを使ってiPhoneをiBeaconとして設定してあげると検出できるとのこと。
raspberry pi - Detecting proximity of an iPhone 5s from a RaspberryPi - Stack Overflow
Beacon Toolkitを App Store で

意味がわかりませんがものは試しということでさっそくやってみました。
f:id:gadgetcat:20160105050604p:plain
Beacon Toolskitにて適当なビーコンを設定。
RPiにてlescan。
あっさり見つかる。

???

つまりlescanはビーコンを探すコマンドなの??
いや、そもそもBLEってのがモードの一種ってこと?
lescanってのはBLEのみを探すの??
Beacon ToolkitでiPhoneをBLEモードで実行させたから検出できたってこと?

混乱してきました
これちょっと後で調べる

iPhoneとのペアリング

ペアリングには最初blueman(GUIで簡単にBluetoothの設定ができるパッケージ)を使って行おうとしましたが、
iPhone側に一瞬ダイアログが表示されてすぐに切断されるという現象が発生し、失敗に終わりました。
grepでエラーを見るとDBusFailedError: No such file or directory
などと言っておりbluemanのヘルプサイトにて

DBus エラー
おそらく DBus を使って bluetooth デーモンと対話するために必要なグループにユーザーが属していません。このグループは /etc/dbus-1/system.d/bluetooth.conf で定義されています:
$ cat /etc/dbus-1/system.d/bluetooth.conf | grep "group="

グループが lp の場合、ユーザーアカウントにグループを追加してください:
# gpasswd -a [user] lp

と言っていたので対応するも変わらず。

諦めてbluezとセットでインストールされたbluetoothctlを使いました。
(bluetoothctlは対話式のツールなので、めんどくさいと思って最初使いませんでした)
最初コマンドから

[bluetooth]# scan on

にてデバイスを検索し、見つかったiPhoneに対し接続を試みましたが、
bluemanと同様iPhone側に一瞬ダイアログが表示されてすぐに切断されるという現象が発生。
Bluetooth ヘッドセット - ArchWiki

ペアリングは成功するが、接続が失敗する
bluetoothctl で以下のエラーが表示される場合:
[bluetooth]# connect 00:1D:43:6D:03:26
Attempting to connect to 00:1D:43:6D:03:26
Failed to connect: org.bluez.Error.Failed

Grepでエラーを見ると

a2dp-source profile connect failed

とか言っているので、なんかよくわからないプロファイルで繋ごうとしてるのかと思いましたが、
Intel EdisonでBluetoothを試してみる | Workpiles
こちらのサイトを見て、agentをPIN番号要求ではなくDisplayYesNoに設定してみるとあっさり繋がる。
なぜ???

[bluetooth]# agent DisplayYesNo
[bluetooth]# pair <dev>
[bluetooth]# connect <dev>

bluetoothctlにてagent DisplayYesNoを行うことが肝だったみたい。
意味がわからないのでこれも後で調べよう
bluemanでもこのあたりの設定を変えれば繋がったんだろうか。。。
(そんなのネットに書いてなかったし、自分の環境依存問題?)

そしてiPhoneからのBT検索ではまったくRPiが見つからないので、
(WindowsWindows Phoneからは見つかる)これも課題だなぁ

Raspberry Pi + Bluetooth

とある目的のためにRapberry PiにBluetooth ドングルを差してiPhone等とコミュニケーションさせたい。

そのために行ったことをつらつらと。

 

H/W、S/W構成

・Raspberry Pi 2 Model B

OS:Raspbian Jessie

Varsion:Linux raspberrypi 4.1.13-v7+ #826 SMP PREEMPT Fri Nov 13 20:19:03 GMT 2015 armv7l GNU/Linux

Bluetooth ドングル Princeton PTM-UBT7

 過去に別用途で購入(たしか安かったから)したものを引用。一応Bluetooth Low Energy(BLE)対応っぽいけど苦労したくないならPlanex辺りを購入した方が無難か

BlueZ のインストール

まず現在bluezがインストールされていないことを確認

dpkg -l | grep blue

BluezはLinuxで一般的に使われているBluetoothスタックらしいです。apt-getでインストールできるものは古そうだったので、ビルドすることにしました。

まずいろいろ作業するためにDocuments内に作業ディレクトリを作成

cd Documents/

mkdir -p work/bluetooth

  

記事執筆時点(2016/01/02)での最新バージョンはどうやら5.37の模様。

https://www.kernel.org/pub/linux/bluetooth/

ここで最新のものを選択して取ってくる。

$ wget http://www.kernel.org/pub/linux/bluetooth/bluez-5.37.tar.xz

取ってきたら解凍して

$ tar xvf bluez-5.37.tar

解凍したソースディレクトリ内に入り、コンフィグを設定

$ cd bluez-5.37/

$ ./configure --disable-systemd --enable-library

依存関係とかいろいろ確認されるが、自分の環境では以下のエラーが発生

checking for linux/if_alg.h... yes
checking for GLIB... no
configure: error: GLib >= 2.28 is required

GLibが存在しないらしいので、関連するものをまとめて入れ込み(そもそも先に入れておくものらしい)

$ sudo apt-get install libdbus-1-dev libdbus-glib-1-dev libglib2.0-dev libical-dev libreadline-dev libudev-dev libusb-dev make

ごちゃっと

再度コンフィグを設定すると、

$ ./configure --disable-systemd --enable-library

checking for GLIB... yes

checking that generated files are newer than configure... done

今度はうまくいったっぽい。次はビルドしてインストール。

makeで10~15分ほどかかります。

$ make

$ sudo make install

続けてmakeでインストールされないものを手動でインストール

$ sudo cp attrib/gatttool /usr/local/bin/

libbluetooth-dev の各種ヘッダファイルをビルドしたもので置き換え

$ sudo cp -ipr lib/ /usr/include/bluetooth.5.37

$ cd /usr/include

$ sudo ln -s bluetooth.5.37/ bluetooth

BTドングルの状況をチェック

$ lsusb
Bus 001 Device 005: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 001 Device 004: ID 0d8c:0014 C-Media Electronics, Inc.
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

 Device 005にBTドングルさんがいますね。

ドングル詳細情報はID指定で

$ sudo lsusb -v -d 0a12:

状態は

$ hciconfig
hci0: Type: BR/EDR Bus: USB
BD Address: 00:1B:DC:XX:XX:XX ACL MTU: 310:10 SCO MTU: 64:8
DOWN
RX bytes:580 acl:0 sco:0 events:31 errors:0
TX bytes:368 acl:0 sco:0 commands:30 errors:0

 DOWNなので認識してるけど寝ている状態ですかね。

MACアドレスはマスクしてます)

BTドングルの起動

$ sudo hciconfig hci0 up
$ hciconfig
hci0: Type: BR/EDR Bus: USB
BD Address: 00:1B:DC:XX:XX:XX ACL MTU: 310:10 SCO MTU: 64:8
UP RUNNING
RX bytes:1154 acl:0 sco:0 events:61 errors:0
TX bytes:736 acl:0 sco:0 commands:60 errors:0

 UP RUNNINGになったので起動できたと思われます。

Bluetooth デバイスのスキャン

 $ sudo hcitool lescan
LE Scan ...

本来ここで手持ちのBLEデバイスたちが表示されるはずが、自分は特に所有していなかったため何も表示されず(ちゃんと動いてんのかこれ、、、)

$ hcitool scan

では一応手持ちのiPhone5等が表示されました。

(あれっiPhone5ってBLE対応してないんだっけ?)

  

また、デバイスからRPiを発見できるように

sudo hciconfig hci0 piscan

にて設定。

 

参考にさせていただいたサイト

kakakikikekeのブログ: RaspberryPi に BlueZ をインストールして BLE を検知してみた

 

次はPCやiPhone等別デバイスとのコミュニケーションを行う予定です。