digital matter
Internet Explorer 6さようなら運動はじめました!

ArduinoとEthernetシールドとDHCPとDNS

以前書いた Arduino DHCP Libraryを使ってみる : blog.loadlimit – digital matter - で使ったライブラリが、最近のIDEで使えなくなったということで、他のライブラリを見つけました。

gkaindl.com → software → arduino ethernet

前述のDHCP Libraryをベースに改良されたもので、機能も拡張されています。

ダウンロード後、解凍して、中身のフォルダをArduinoのlibrariesフォルダにコピーします。

あとはただただソースを書くだけ。

DHCPの更新処理とかを自動でやってくれるmaintain()が便利です。こんな感じのコードになりました。
#if defined(ARDUINO) && ARDUINO > 18
#include <SPI.h>
#endif
#include <Ethernet.h>
#include <EthernetDHCP.h>
#include <EthernetDNS.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

const char* hostName = "sample.com";

void setup()
{
  delay(100);
  Serial.begin(9600);

  EthernetDHCP.begin(mac);
}

void loop()
{
  EthernetDHCP.maintain();

  const byte* dnsAddr = EthernetDHCP.dnsIpAddress();
  EthernetDNS.setDNSServer(dnsAddr);

  static bool isDnsResolved = false;
  static byte remoteAddr[4];

  if (!isDnsResolved) {

    DNSError err = EthernetDNS.sendDNSQuery(hostName);

    if (DNSSuccess == err) {
      do {
        err = EthernetDNS.pollDNSReply(remoteAddr);

        if (DNSTryLater == err) {
          delay(20);
          Serial.print(".");
        }
      } while (DNSTryLater == err);
    }

    if (DNSSuccess == err) {
      isDnsResolved = true;
    }

  }

  if (isDnsResolved) {

    Client client(remoteAddr, 80);

    Serial.println("connecting...");

    if (client.connect()) {
      Serial.println("connected");

      client.println("GET / HTTP/1.0");
      client.println();

      // 返事が戻ってくるまで待機
      do {
        if (client.available()) {
          char c = client.read();
          // ここで何かする
        }
        if (!client.connected()) {
          Serial.println();
          Serial.println("disconnecting.");
          client.stop();
          break;
        }
      } while (true);
    }
    else {
      Serial.println("connection failed");
      delay(2000);
    }

  }
}

DNSの更新は見ていないのでDDNS環境のサーバを長時間監視する場合はそれなりの処理が必要です。

それと、イーサネットシールドがPCに接続していない場合にIPを取れない問題は、新しいバージョンのイーサネットシールドでは修正されていたようです。

スイッチサイエンス/商品詳細 Arduino イーサネットシールド

古いバージョンを使っている人は、下記のサイトに対策が載っていました。自分もコンデンサひとつとダイオードひとつ追加してみたら正常に動作するようになりました。

Arduino Ethernet Shieldのパワーオンリセット: PS3とLinux、電子工作も

関連する投稿

AndroidアプリからFacebookアプリに画像をキャプション付きで投稿する

Androidにて、ActivityからFacebookにギャラリーの画像をシェアする際に、画像の説明であるキャプションを指定する方法がわからなかったので調べました。

結論としてはIntent.EXTRA_TITLEを指定すれば良いようです。

TwitterやメールではIntent.EXTRA_TEXTとかなので、そのままだとFacebookアプリには反映されません。

String text = "Some caption...";
Uri path = Uri.parse("content://media/external/images/media/1"); // Gallery's URI etc.

Intent intent = new Intent(Intent.ACTION_SEND);

// キャプション
intent.putExtra(Intent.EXTRA_TITLE, text);

// 添付ファイル
intent.setType("image/jpeg");
intent.putExtra(Intent.EXTRA_STREAM, path);

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(Intent.createChooser(intent, getString(R.string.post_facebook)));

関連する投稿

sfFlickrPluginのRequest failed.に対処する

symfony 1.4にて、Flickrから画像を取得して使いたかったので、sfFlickrPluginを使ってみました。

sfFlickrPluginのインストール方法はこちらのサイトを参考にチェックアウトできます。

symfonyでFlickrを使用するためのPluginまとめ – Layer8 Reference

基本的には手動によるセットアップです。
plugins/に展開します。
自分はエクスポートしました。

$ cd plugins/
$ svn export http://svn.symfony-project.com/plugins/sfFlickrPlugin

キーに関してはさきほどのサイトを参考に。

あとは

config/ProjectConfiguration.class.php

に、enablePluginsを追加すればOKです。

  public function setup()
  {
    $this->enablePlugins('sfDoctrinePlugin');
    $this->enablePlugins('sfFlickrPlugin');
  }

で、セットアップ完了。

ここから表題の話。

グループから写真を取得したい場合はgetGroupPhotoListを使います。

が、getGroupPhotoListを実行すると、

Request failed. The requested URL returned error: 413

というエラーで停止。

plugins/sfFlickrPlugin/lib/vendor/Phlickrを最新に差し替えることで解決します。

Phlickrはこちらにあります。

Phlickr | Download Phlickr software for free at SourceForge.net

Phlickrの最新版は0.2.8だったので、丸ごと上書きします。

ファイル一式とFramework、TextUiディレクトリも上書き

それ以外のディレクトリは調べてないのでわからないですが、まぁ、置いておけば良いのでは。

Testsは、いらない雰囲気。

これで正常に動くようになりました。

あと、桁あふれの問題があるので、

PhlickrのbuildImgUrlでPhoto IDが桁あふれ : blog.loadlimit – digital matter -

を参考にして修正してください。

関連する投稿

mod_rewriteでブラウザの言語別にリダイレクトする

ユーザーがサイトのあるページ(http://sample.com/hoge/)に来た際に、ブラウザの優先言語が日本語の場合はjaページ(http://sample.com/hoge/ja/)、英語やそれ以外の言語の場合はenページ(http://sample.com/hoge/en/)に飛ばす方法のメモ。

やり方は以下のサイトから。

Redirect according to browser language: mod rewrite and HTTP_ACCEPT_LANGUAGE | Michal Borychowski

本来は

RewriteCond %{HTTP_ACCEPT_LANGUAGE}

とか書きたいところを、この変数がApacheに存在しないので、

RewriteCond %{HTTP:Accept-Language}

にするのが肝らしいです。

で、書いてみた.htaccessファイルはこちら。

<IfModule mod_rewrite.c>
  RewriteEngine On

  RewriteCond %{HTTP:Accept-Language} ^ja [NC]
  RewriteRule ^$ /hoge/ja/ [L,R=301]

  RewriteRule ^$ /hoge/en/ [L,R=301]
</IfModule>

FireFox,IE,Chromeで問題なさそうだったのでOK。

関連する投稿

Androidのライブ壁紙をActivityから指定したい

アプリの一覧からアイコンを押したら、ライブ壁紙をプレビュー→壁紙に設定できる状態にできないかという話題。

ちなみに結論としてはパーミッション不足により失敗です。

ライブ壁紙の選択画面を表示させることはできるので、
Live Wallpaper – Binding an Activity to the ‘Open’ button of the market
こちらを参考にライブ壁紙選択画面の上にカスタムビューなどを表示させて誘導させるのが次善の策かと思います。

ライブ壁紙選択画面を表示するだけなら

Intent intent = new Intent();
intent.setAction(WallpaperManager.ACTION_LIVE_WALLPAPER_CHOOSER);
startActivity(intent);

でOK。

以下は失敗の記録。

上で書いたライブ壁紙選択画面表示のコードは 
LiveWallpaperをアプリから設定する方法を探ってみた|いろいろ備忘録

こちらの方法でも代替できます。

LiveWallpaperListActivityのソースコードを持ってきて追いかけてみることにしました。

LiveWallpaperListActivity.java

これを同じパッケージ内に設置して、layoutやdimen、color等を適当に用意してやることで、選択画面は同じ物を再現できます。

一応、用意したファイル

live_wallpaper_list.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingLeft="6dip" android:paddingRight="6dip" android:paddingTop="6dip" android:paddingBottom="6dip" android:minHeight="?android:attr/listPreferredItemHeight">
<ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:drawSelectorOnTop="false"/>
<TextView android:id="@android:id/empty" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:visibility="gone" android:text="@string/live_wallpaper_empty" android:textAppearance="?android:attr/textAppearanceMedium"/>
</LinearLayout>

live_wallpaper_entry.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingLeft="6dip" android:paddingRight="6dip" android:paddingTop="6dip" android:paddingBottom="6dip" android:minHeight="?android:attr/listPreferredItemHeight">
<ImageView android:id="@+id/thumbnail" android:layout_width="@dimen/live_wallpaper_thumbnail_width" android:layout_height="@dimen/live_wallpaper_thumbnail_width" android:layout_gravity="center_vertical" android:scaleType="centerCrop"/>
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dip" android:layout_marginRight="8dip" android:layout_centerVertical="true" android:layout_toRightOf="@id/thumbnail" android:orientation="vertical">
<TextView android:id="@+id/title_author" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:singleLine="true" android:ellipsize="marquee"/>
<TextView android:id="@+id/description" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:maxLines="3"/>
</LinearLayout>
</RelativeLayout>

color.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="live_wallpaper_thumbnail_background">#000000</color>
    <color name="live_wallpaper_thumbnail_text_color">#ffffff</color>
</resources>

dimens.xml (値は適当にいれた)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="live_wallpaper_thumbnail_width">50dp</dimen>
    <dimen name="live_wallpaper_thumbnail_height">50dp</dimen>
    <dimen name="live_wallpaper_thumbnail_text_size">50dp</dimen>
    <dimen name="live_wallpaper_thumbnail_text_offset">50dp</dimen>
</resources>

で、このままでは

LiveWallpaperPreview.showPreview(this , REQUEST_PREVIEW, intent, info);

の行がコンパイルできないのでshowPreviewメソッド(staticメソッド)だけを取り出してきてLiveWallpaperListActivityに

	static final String EXTRA_LIVE_WALLPAPER_INTENT = "android.live_wallpaper.intent";
	static final String EXTRA_LIVE_WALLPAPER_SETTINGS = "android.live_wallpaper.settings";
	static final String EXTRA_LIVE_WALLPAPER_PACKAGE = "android.live_wallpaper.package";

	public void onItemClick(AdapterView<?> parent, View view, int position,
			long id) {
		final Intent intent = mWallpaperIntents.get(position);
		final WallpaperInfo info = mWallpaperInfos.get(position);
		//LiveWallpaperPreview.showPreview(this, REQUEST_PREVIEW, intent, info);
		//Intent preview = new Intent(this, LiveWallpaperPreview.class);
		String clazz = "com.android.wallpaper.livepicker.LiveWallpaperPreview";
		Intent preview = new Intent(Intent.ACTION_MAIN);
		int idx = clazz.lastIndexOf('.');
		String pkg = clazz.substring(0, idx);
		preview.setClassName(pkg, clazz);
		preview.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		preview.putExtra(EXTRA_LIVE_WALLPAPER_INTENT, intent);
		preview.putExtra(EXTRA_LIVE_WALLPAPER_SETTINGS,
				info.getSettingsActivity());
		preview.putExtra(EXTRA_LIVE_WALLPAPER_PACKAGE, info.getPackageName());
		this.startActivityForResult(preview, REQUEST_PREVIEW);
	}

先程のサイトを参考に、こんな感じで書き換え。で、実行。

FATAL EXCEPTION: main
java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.MAIN flg=0x10000000 cmp=com.android.wallpaper.livepicker/.LiveWallpaperPreview (has extras) } from ProcessRecord{4073a338 8432:com.sample.android.TestCase/10077} (pid=8432, uid=10077) requires null
    at android.os.Parcel.readException(Parcel.java:1322)
    at android.os.Parcel.readException(Parcel.java:1276)
    at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:1351)
    at android.app.Instrumentation.execStartActivity(Instrumentation.java:1374)
    at android.app.Activity.startActivityForResult(Activity.java:2827)
    at com.sample.android.TestCase.LiveWallpaperListActivity.onItemClick(LiveWallpaperListActivity.java:201)
    at android.widget.AdapterView.performItemClick(AdapterView.java:284)
    at android.widget.ListView.performItemClick(ListView.java:3513)
    at android.widget.AbsListView$PerformClick.run(AbsListView.java:1812)
    at android.os.Handler.handleCallback(Handler.java:587)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:130)
    at android.app.ActivityThread.main(ActivityThread.java:3683)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:507)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
    at dalvik.system.NativeStart.main(Native Method)

とりあえずライブ壁紙の一覧とアイコンは取れるってことはわかりましたね…

関連する投稿

Twitter+oauth-signpostでOAuthCommunicationException?

oauth.signpost.exception.OAuthCommunicationException: Communication with the service provider failed: https://api.twitter.com/oauth/request_token

Application TypeをBrowserにして、Callback URLをダミーでいいので入力したかい?入力しないとClientに変更されるよ。

Request token URL

https://api.twitter.com/oauth/request_token

Access token URL

https://api.twitter.com/oauth/access_token

Authorize URL

https://api.twitter.com/oauth/authorize

httpsになってるからちゃんとdev.twitter.comから新しいのコピペしようね。

っていう、自分への備忘録。Androidの話。

関連する投稿

Symfony 1.4からZendFrameworkでPicasaWebAlbumsにアクセスする

symfony-projectのページがGoogle検索に引っかかりにくい(引っかかってもバージョン違ったりする)ので色々キーワード入れてメモ。

ZendFrameworkをSymfonyに導入する方法は以下のJobeetの17日目に書いてありました。

Practical symfony | 17日目: 検索 | symfony | Web PHP Framework

cd lib/vendor/
wget http://framework.zend.com/releases/ZendFramework-1.11.7/ZendFramework-1.11.7.zip
unzip ZendFramework-1.11.7.zip
mv ZendFramework-1.11.7/library/Zend/ .

とりあえずlib/vendorに入れておきました。

あとはconfig/ProjectConfiguration.class.phpに上記ページのコードを貼りつけて、使う直前にProjectConfiguration::registerZend();してやればOK。

Picasaの方は

Zend Framework: Documentation: Picasa Web Albums の使用法 – Zend Framework Manual

このページを参考にしてできます。

丸投げだけど日本語だしわかりやすいので特に解説も不要かな、と…

関連する投稿

PHPでFlickrからPhlickrを使ってグループ検索

Phlickrを使って、グループIDとライセンス指定で写真を検索してくる方法です。

$api = new Phlickr_Api(API_KEY, API_SECRET);

$request = $api->createRequest('flickr.photos.search', array(
    'group_id' => GROUP_ID, // グループID
    'license' => '4,5,6', // ライセンス設定(商用利用可)
));
$photoList = new Phlickr_PhotoList($request, 500); // 1ページ500件

$pageCount = $photoList->getPageCount();
for ($i = 1 ; $i < $pageCount ; $i++) {
    $photos = $photoList->getPhotosFromPage($i);
    foreach ($photos as $photo) {
        // something nice
    }
}

関連する投稿

PhlickrのbuildImgUrlでPhoto IDが桁あふれ

PHPでFlickrを簡単に扱えるPhlickrを使っているのですが、Phlickr_PhotoクラスのbuildImgUrlで取得した画像のURLにアクセスしたら、「This photo is currently unavailable」の文字。

URL中のPhoto IDの入る箇所が2147483647(0x7FFFFFFF)になっていました。

ということでPhoto.phpを修正。551行目。

        $url = sprintf("http://farm%d.static.flickr.com/%d/%d_%s%s.%s",
            $this->getFarm(), $this->getServer(), $this->getId(), $this->getSecret(), $sizeStr, $type);

3番目の%dを%sに。

        $url = sprintf("http://farm%d.static.flickr.com/%d/%s_%s%s.%s",
            $this->getFarm(), $this->getServer(), $this->getId(), $this->getSecret(), $sizeStr, $type);

これで解決。

関連する投稿

Symfonyでembed18nを使う

Symfonyで多言語対応する予定があるけど、とりあえず日本語だけ使いたかったときのメモです。embedI18nを自在に使う参考に。例によってSymfony 1.4+Doctrine。

// HogeForm.class.php
public HogeForm extends BaseHogeForm
{

    public function configure()
    {
        parent::configure();

        $this->embedI18n(array('ja'));

        // 二次元配列でembedしたフォームにアクセスできる
        $this->validatorSchema['ja']['name']->setOption('required', true);

        $this->useFields(array('ja', 'foo', 'bar'));
    }

}

このままテンプレートで$form->render()とかしてしまうと、入れ子表示になってしまうのでrenderRowでそれぞれ表示するようにしてやれば、useFieldsでembedされたフォームの順番がいじれないという問題も解決。

// hogeCreateSuccess.php
$form['ja']['name']->renderRow();
$form['foo']->renderRow();
$form['bar']->renderRow();

ちなみにsfFormDoctrine::getI18nFormClass()とかあるのですが、クラス名が取れるだけです。

追記

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry ’19-ja’ for key ‘PRIMARY’

フォームをsaveしたら上記のエラーが出ました。

updateObject後にobject->toArray()をすると見覚えのない、「ja_JP」というキーが。

Array
(
    [id] =>
    [foo] => hoge
    [bar] => moge
    [Translation] => Array
        (
            [ja] => Array
                (
                    [id] =>
                    [name] => someone
                    [lang] => ja
                )
            [ja_JP] => Array
                (
                    [id] =>
                    [name] =>
                    [lang] => ja_JP
                )
        )
)

色々調べた結果、原因は

  • langがchar(2)なので、jaとja_JPの区別がつかないこと
  • sfDoctrineRecord::getDefaultCulture()がja_JPなこと

恐らく、

http://www.symfony-project.org/jobeet/1_4/Doctrine/ja/19

このページを参考にしているとハマるのではないかと…

ちなみにlangがchar(2)なのは、Doctrine_I18nの中で定義されています。

対策として考えられるのは以下のパターン。

  • sfDoctrineRecord::setDefaultCulture(‘ja’)をコールする
  • langをchar(5)にしてja_JPの形式で扱う
  • settings.ymlのdefault_cultureをjaにする
  • embedI18nにja_JPを渡す

sfDoctrineRecordのカルチャをセットするのは、sfUserのカルチャをセットすることでイベントが駆動して同時にセットできます。なので、$this->getUser()->setCulture(‘ja’)とかしておけばOK。

langをchar(5)にするのは、I18nビヘイビアのオプションにlength:5を指定すればできます。この場合、カルチャをすべて5文字で扱うようにしないとlangがマッチしなくなります。length:2の場合は暗黙的に先頭2文字だけにマッチさせることで5文字のカルチャを許容していたので。

# schema.yml
Sample:
  actAs:
    Timestampable: ~
    I18n:
      fields: [name]
      length: 5

settings.ymlのdefault_cultureをjaにするのが一番簡単です。en_USとen_GBはどうするんだという話ですけど…

formクラスに$this->embedI18n(array(‘ja_JP’))とすることでも対応できます。

気になる方はsfDoctrineRecordI18nFilterクラスのfilterSet関数を見てください。

カタログファイルとかにも影響しそうなのでsettings.ymlに2文字カルチャをセットするのがベターかなぁ。

関連する投稿