HttpListener と TcpListener の違いと Firewall と netsh の設定

PC でネットワーク通信をするときは IIS を使って HTTP プロトコルを使うのが定番なのですが、途中に IIS みたいな Web サーバーが入ってしまうのが難点です。いや、ASP.NET MVC や Web API レベルで C/S 作っている分にはいいけど、昔からの TCP/IP を使わないと実現できない(実現しやすい)というパターンを想定して、HttpListener と TcpListener を改めて使ってみます。

HttpListener を使う

HTTPプロトコルを 8188 ポートで受けて、Win キーを押すというサンプルコードです。Win キーを押すのはおまけで、まあ、こんな感じで独自の HTTP サーバーが作れるという意味ですね。

private void button1_Click(object sender, EventArgs e)
{
    string url = "http://+:8188/";
    sv = new HttpListener();
    sv.Prefixes.Add(url);
    this.listBox1.Items.Add("start: " + url);
    task = new Task(
        () =>
        {
            this.sv.Start();
            while (loop)
            {
                var cont = this.sv.GetContext();
                Debug.WriteLine("url:" + cont.Request.Url);
                this.listBox1.Invoke(new AddListItem(
                    () =>
                    {
                        this.listBox1.Items.Add(cont.Request.Url);
                    }));

                var cmd = cont.Request.Url.Query;
                cmd = cmd.Substring(1);
                var dic = System.Web.HttpUtility.ParseQueryString(cmd);
                var val = dic["key"];
                switch (val)
                {
                    case "HttpKey":
                        this.Invoke(new Click(() =>
                        {
                            SendKeys.Send("^{ESC}");
                        }));
                        break;
                }
                using (var sw = new StreamWriter(cont.Response.OutputStream))
                {
                    sw.WriteLine("OK");
                }
                cont.Response.Close();
            }
            this.sv.Stop();
        });
    task.Start();
}

動かすときは、管理者モード&ローカル環境で動作確認するのが手っ取り早いです。というのも、別のPCから送信する場合は、Firewall と netsh の設定が必要になって結構ややこしいのです。
が、結構ややこしいところは避けて通れないので、後で解説します。

クライアントは、ブラウザに http://localhost:8188/sendkey?key=HttpKey な感じで呼び出せば ok です。
SendKeys.Send を送るときは一旦 Invoke しないと呼び出せないようです。new Click はデリゲート型です。

TcpListener を使う

同じものを TCP/IP のリスナー TcpListener を使って実装するとこんな感じです。今度は 8189 ポートで待ちます。

private void button2_Click(object sender, EventArgs e)
{
    this.tcp = new TcpListener(IPAddress.Any, 8189);
    this.listBox1.Items.Add("start: " +"tcp" );
    tcp.Start();

    task = new Task(() => {
        while (loop)
        {
            var sock = tcp.AcceptSocket();
            byte[] data = new byte[1024];
            sock.Receive(data);
            var url = System.Text.Encoding.ASCII.GetString(data);
            this.listBox1.Invoke(new AddListItem(
                () =>
                {
                    this.listBox1.Items.Add(url);
                }));
            // 実際はもうちょっとパースしないと駄目
            var cmd = url.Substring(url.LastIndexOf("/") + 1);
            cmd = cmd.Substring(1);
            var dic = System.Web.HttpUtility.ParseQueryString(cmd);
            var val = dic["key"];
            switch (val)
            {
                case "WinKey":
                    //SendKeys.Send("^{ESC}");
                    break;
            }
            var res = "OK";
            data = System.Text.Encoding.ASCII.GetBytes(res);
            sock.Send(data);
            sock.Close();
        }
    });
    task.Start();
}

同じことができるならば、HttpListener でも TcpListener でもどちらでも良いような気がするのですが、実は違います。もちろん、TcpListener のほうでバイナリデータを渡せるという利点も捨てがたいのですが、いまとなっては HTTP プロトコルで XML 形式で渡したほうが手軽でしょう。まあ、ストリーミングなんかは TCP/IP にしないと駄目なんですが、Win キーを押させるぐらいの単発動作ならば、どちらでやっても構いません。

が、いざ、別の PC から送信しようとすると、Firewall という壁があってそれぞれの設定が異なります。

Surface RT から PC にキー送信する

こんな風に、Surface RT のストアアプリから、別の PC のストアアプリを操作しよう、というのを考えました。PC のほうのストアアプリにリスナーを追加することはできないので、デスクトップにリスナー用のアプリをかませます。PC 内でのデスクトップアプリとストアアプリとの連携をどうするのか?は懸念事項ですが(たぶん、ショートカットキーの送信で済ませる予定)、タブレットから扱えると便利かなと。
タブレット自体は、iPhone でも Android でも良いわけですが、ひとまず Surface RT のストアアプリを想定します。配布が楽ですからね。

ここで、問題になるのが真ん中にはさまっている Firewall です。無効にすれば簡単(笑)なのですが、まあ配布するとなるとそうはいかないし、この際だからきっちりと Firewall の設定も済ませよう、という魂胆です。

HttpListener の場合は、Firewall と netsh の両方の設定が必要

HttpListener の場合は、HTTP プロトコルが通るので netsh http を使って HTTP プロトコルのアドレスを開けると同時に、Firewall を開けないといけません。なんとなく両方とも開けないと駄目なことが分かるのですが、実は HttpListener は Http.sys を通しているので設定が曲者です。
HTTP.SYS は、HTTP プロトコルを扱う特別なカーネルプロセスで、これが HTTP プロトコルを特別扱いしているための、Firewall の設定が一筋縄ではいきません。

まずは、netsh http を使って http://+:8188/ の呼び出しが有効になるようにします。呼び出し元を絞ることができるのですが、ここでは everyone を指定してすべてのユーザーを有効にしておきます。

netsh http add urlacl url=http://+:8188/ user=everyone

コントロールパネルでファイアウォールを開いて「詳細設定」をクリックします。

受信の規則で目的のルールをクリックするか新規に作成して、次のプログラムを「system」に書き換えます。

これを通常の *.exe ファイルにしておくと HttpListener を使ったときに Firewall が通りません。

C# HttpListener and Windows Firewall – Stack Overflow
http://stackoverflow.com/questions/17863294/c-sharp-httplistener-and-windows-firewall

理由は簡単で、HttpListener を使っているときは HTTP.SYS が一括して HTTP プロトコルを管理するので、HTTP.SYS のほうの Firewall を設定しないと駄目なんですよね。
で、せっかくプログラム単位でポートを絞っていたのが、HTTP.SYS 単位になってしまうので穴が大きくなってしまいます。仕方がないので「プログラムおよびポート」のほうで、許可するポートを絞っておきましょう。

ここでは、TCP の 8188 のポートだけを通すようにします。

netsh http のほうですが、本来は、http://+:8188/sendkey/ のように特定のアドレスを通すようにします。こうするほうがセキュリティが高いのですが、HttpListener を使うと 8188 ポートを占有してしまうので、他のアプリでこのポートで待つことは当然できなくなります。なので、あまりアドレスを書いても意味がないので、http://+:8188/ な感じでポートだけ指定しています。この firewall + netsh の書き方では、どのプログラムも通るようになるので、別のプログラムを使って先に同じポートを使うことができます。排他的ですが共有できるということですね。逆に言えば、他のプログラムに使われてしまうリスクがあるので、セキュリティが低いとも言えます。

TcpListener の場合は Firewall だけを設定する

TcpListener でリスナーを作るときは Firewall だけを設定します。

指定プログラムだけ通すので、安全といえば安全ですよね。HTTP.SYS の場合も、こんな風にできればいいのですが、ちょっと無理みたいです。
ただし、動かしてみたいときは、TcpListener の呼び出しにちょっと時間がかかります。たぶん、プログラムの起動時とファイアウォールの設定を再読み込みすることになるので、スタートだけ遅いって感じなんでしょう。

HttpListener と同じようにポートで絞ることもできるのですが、プログラム単位で絞ってあるので必要はないでしょう。このプログラム自体をすり替えれば別の動きもできるのですが、まあ、それはバージョンアップ時も再設定がいらないということです。

スクリプトを使って netsh http を設定する

windows – C# HttpListener without using netsh to register a URI – Stack Overflow
http://stackoverflow.com/questions/2583347/c-sharp-httplistener-without-using-netsh-to-register-a-uri/2782880

netsh http add urlacl url=http://+:8188/ user=everyone

削除するときは

netsh http delete urlacl url=http://+:8188/

スクリプトを使って firewall を設定する

HTTP.SYS を使う時は program=system を設定する。

netsh advfirewall firewall add rule name="SendKeySv HTTP" dir=in action=allow
netsh advfirewall firewall set rule name="SendKeySv HTTP" new ^
	program=system ^
	profile=private ^
	protocol=tcp localport=8188

TcpListener を使う場合は、program=<プログラム名>にしておく。

netsh advfirewall firewall add rule name="SendKeySv TCP" dir=in action=allow
netsh advfirewall firewall set rule name="SendKeySv TCP" new ^
	program=<プログラム名> ^
	profile=private

コマンド自体は、netsh http add urlacl などと打つとヘルプが出るので調べられる。

設定自体を見るときは、show コマンドを使えばよい。ファイルに落とす dump コマンドがあるけども、内部実装はされていません。これはヘルプにも記述がある。

> netsh http show urlacl
    予約済み URL            : http://+:8089/
        ユーザー: Everyone
            リッスン: Yes
            委任: No
            SDDL: D:(A;;GX;;;WD)

 

D:temp>netsh advfirewall firewall show rule name="SendKeySv HTTP"

規則名:                               SendKeySv HTTP
----------------------------------------------------------------------
有効:                                 はい
方向:                                 入力
プロファイル:                         プライベート
グループ:
ローカル IP:                          任意
リモート IP:                          任意
プロトコル:                           TCP
ローカル ポート:                      8188
リモート ポート:                      任意
エッジ トラバーサル:                  いいえ
操作:                                 許可
OK

プログラムから runas を使って管理モードで動かす

インストーラを使うときは、管理者モードのプロンプトが必要になるので runas を使う。

private void button4_Click(object sender, EventArgs e)
{
    string args = "http add urlacl url=http://+:8188/ user=everyone";
    ProcessStartInfo psi = new ProcessStartInfo("netsh", args)
    {
        Verb = "runas",
        CreateNoWindow = true,
        WindowStyle = ProcessWindowStyle.Normal,
        UseShellExecute = true
    };
    Process.Start(psi).WaitForExit();
}

あえて、ProcessWindowStyle.Normal を使っているけど Hidden にするとウィンドウは表示されない。UAC(ユーザアカウント制御)を有効にしておくと(大抵のユーザーは「有効」のまま)、管理者モードで動かしますか?のダイアログが表示されるので「はい」を押してもらう。

ひとまず、これで外部の PC から HttpListener に送信するプログラムが作れる&動作します。

カテゴリー: C# | 1件のコメント

[WinRT] ストアアプリの WebView の中身を取得する方法

絶版マンガ図書館
http://zeppan.com/

というのがあって、絶版になった本を集めて公開しています。いろいろ経緯は絶版マンガ図書館を読んで貰うとして、この本には広告が挟み込んであってそれが収入代わりになっています。絶版本なので、古い漫画が多いのですが、漫画歴の長い私としては懐かしい本やらファンな本があって、何かと手伝いたい感じだったので、勢いで作ってみました。いや、単に Kindle Launcher と同じ形式にしたできた、ってだけなんですけどね。

絶版マンガ図書館 Launcher
http://apps.microsoft.com/windows/ja-jp/app/launcher/69c16e83-005c-49a3-ac83-6414b08d89fd

Windows 8.1 以降でどれくらいの人が読むのか?は謎なのですが、まあ良しとしましょう。

確か、Jコミの頃から API とかは無かったんですよね。なので、清く HTML ページからサルベージして情報を抜き出すようにしています。レイアウトが変わったり、id や class が変わると呼び出せなくなってしまうのですが、まあ、そのときはそのときに追随するとして。

■ストアアプリで WebView を使う

データを取得するときには、HttpClient を使うか、WebView を使って間接的に使うかします。今回の場合、書式が HTML 形式なので、WebView を使うことにしました。WinRT の場合、手軽な HTML パーサがないのが難点ですよね。処理が XML にまとまっているのはいいのですが、こんな風な時はちょっと手間です。

ただし、試してみると、WebView はコード上で new をしても使えることがわかりました。つまりは画面に出さなくても使えるんですよね。まあ、非表示にして使ってもいいのですが、今回のコードでは中で new しています。

private void OnButtonItemSearch(object sender, RoutedEventArgs e)
{
    string text = vm.Title;
    if (text == "") return;
    string url = string.Format("http://www.zeppan.com/title/?query={0}&submit=",
        System.Net.WebUtility.UrlEncode(text));
    var web = new WebView();
    web.NavigationCompleted += async (_,__ ) => {
        string[] para = { "$('#title').html();" };
        string html = await web.InvokeScriptAsync("eval", para);
        Debug.WriteLine(html);

        html = html.Replace(".jpg">", ".jpg"/>").Replace("<br>", "<br/>");
        html = "<root>" + html + "</root>";
        var books = GetBooks(html);

        this.vm.Books = books;
    };
    web.Navigate(new Uri(url));
}

問題は、WevView の中身を取り出す方法です。実は、Microsoft のサンプルにこの方法が乗っています。Javascript をインジェクションして eval 実行させるという技ですね。なるほど。サンプルには document.title のような簡単な例があったのですが、コード見て分かるように jQuery が動きます。と言いますか、サイトが jQuery を使っていると、こんな風に手軽に HTML コードが取り出せるよ、という例です。

ただ、ここで難点なのは、HTML 文字列を取り出した後にパースする手段がないということです。

Deathspike/HtmlAgilityPack-PCL
https://github.com/Deathspike/HtmlAgilityPack-PCL

を使うとパースできるのかもしれません。試していませんが。

仕方がないので、コードを replace して XML パースができる状態にしてしまいます。取り出すときのコードを見る限り img と br が不正になってしまうので、ピンポイントでそこだけ変えています。まあ、jQuery が使えるので、インジェクションする javascript のほうで、適当に XML 形式に直す関数を作って eval するのがよいでしょうね。

■パース済みの XML から ExDoc を使う

数年前からちまちまと作っている/使っているライブラリですが ExDoc で値を取り出します。ExDoc はこういう適当な XML データから適当に値を取り出すのに便利なライブラリです。

BookList GetBooks(string html)
{
    var doc = ExDocument.LoadXml(html);
    var books = new BookList();

    ExElements items  = doc * "div" % "class" == "sakuhin open-detail";
    foreach (var it in items)
    {
        var book = new Book();
        book.Title = ((ExElement)(it * "div" % "class" == "rdtx")).Value.Trim();
        book.Author = ((ExElement)(it * "div" % "class" == "color888")).Value.Trim();
        book.ASIN = it * "div" % "data-baid";
        book.IconUrl = it * "img" % "src";
        book.IconUrl.Replace("thumbnail", "cover");
        books.Add(book);
    }
    return books;
}

■見た目は Kindle Launcher と一緒

UI を考えるのが面倒だったというのもありますが、データバインドさせて GridView で表示しているところは一緒です。Kindle API を使っても、絶版マンガ図書館の HTML を直接弄っても同じように使える、ということです。後は青空文庫まわりで「えあ草子」か「青空文庫リーダー・ライト」を個別呼び出しとか。

カテゴリー: C#, WinRT | [WinRT] ストアアプリの WebView の中身を取得する方法 はコメントを受け付けていません

[WinRT] デスクトップアプリからストアプリへアプリケーションデータを通じて連携させる

Kindle Launcher ネタの続きです。

Windows ストアアプリを公開するときのアカウントを「企業アカウント」にしておくと、デスクトップアプリもストアに紹介を載せることができます。そのために年間9.800円也を支払っていたのですが、いやいやほとんど使わないしやめようと思っていたら、つい先日無料になりました。なので、アカウント自体は放っておくことにしましょう。ちなみに今、出している moonmile solutions のアカウントは「個人アカウント」なのです。企業アカウントの場合、諸々の審査があってブランド名を変えられないので、こっちを使っています。まあ、個人上行主の屋号を変えればいいだけなんですけどね。

ストアプリからデスクトップを扱えなくて、逆にデスクトップからストアアプリへの通知はどうやるのか?という問題が諸々ありますが、ひとつは「プロトコル」を通じて送る方法です。幸いにしてストアアプリのほうはプロトコルを手軽に登録するので、アプリ特有のプロトコルを作っておいて、デスクトップからストアアプリを呼び出せば良いのです。ただし、これの場合は、いちいちストアアプリを起動することになります。
もうひとつの方法は、ストアアプリの「アプリケーションデータ」にデスクトップから直接書き込んでしまうことです。あまり見かけない方法(推奨されない方法?)なのですが、ローミング自体は OS からそういう手段を取っているし、アプリID 自体は固有で作られる(バージョンアップしても変わらない)のでこれを使うのが手軽でしょう。

■ストアアプリのパッケージファミリ名

Package.appxmanifest ファイルをダブルクリックして「パッケージ化」タブを表示させると、パッケージファミリ名が分かります。

ストアアプリをインストールしたときに使われるキーで、アプリで使うアプリケーションデータの位置にも使われています。ストアアプリのデータは、

C:UsersmasudaAppDataLocalPackages37659MoonmileSolutions.KindleLauncher_fjkmb51crq5v0

のように、ログインユーザーの AppData に下に作られるので、ここに直接書き込んでしまいます。Kindle Launcher は、このフォルダの LocalState に書籍の画像ファイルを保存していますが、購入一覧のツールのツールは MyBookshelf.xml で書いています。ここのパッケージ名は Kindle Launcher が公開されている限り同じものが使われるので、このファイルを他にも利用することができます。

アクセス権はユーザーの下にあるので、いくらでも書き込みが可能ですね。他のストアアプリを操作することもできます。RoamingState フォルダに書き込むと、自動的にローミングしてくれるので、適当なストアアプリの RoamingState に書いておいて自動的に同期されるようにする、という裏ワザもできます。

■ストアアプリで起動時に購入履歴を表示する

複数ページの場合は、App.xaml.cs に書くほうがいいのですが、Kindle Launcher は1枚しかないので、MainPage.xaml.cs の LoadState に書いてしまっています。

private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
    // TODO: バインド可能なアイテムのコレクションを this.DefaultViewModel["Items"] に割り当てます
    GetMyBookshelf();
    vm.Title = "";
}
async void GetMyBookshelf()
{
    // MyBookshelf.xml があれば読み込む
    var folder = Windows.Storage.ApplicationData.Current.LocalFolder;
    var path = folder + "\MyBookshelf.xml";

    try
    {
        using (var sr = await folder.OpenStreamForReadAsync("MyBookshelf.xml"))
        {
            var se = new XmlSerializer(typeof(BookList));
            var books = se.Deserialize(sr) as BookList;
            this.vm.Books = books;
        }
    }
    catch
    {
        // 無い場合はそのまま
    }
}

ローカルのアプリケーションフォルダから読み込んでいます。ここでは XmlSerializer を使っていますが、DataContractSerializer を使ってもよいでしょう。気を付けないといけないのは、ファイル自体は「壊れていることがある」ので、必ず復帰ができるようにしておくことです。ここでは try-catch で例外を取得することと、catch した例外は無視することですね。何かエラーがあっても何事もなかったように進まないといけない「フェールセーフ」な作りになります。

カテゴリー: C#, WinRT | [WinRT] デスクトップアプリからストアプリへアプリケーションデータを通じて連携させる はコメントを受け付けていません

[WinRT] Kindle の購入リストを取得する

Kindle Launcher ネタの続きです。

どうせならば、購入済みの一覧を拾ってきて、それに対してセカンダリタイルを付けれたほうが便利ですよね。ということで、購入リストを Kindle Cloud Reader から取得します。
Kindle Cloud Reader 自体がブラウザなので、なんとかなるだろう、という予想のもとに進めて方針を立てます。

  1. WebBrowser を使って Kindle Cloud Reader を表示する。
  2. 中身の DOM を拾って ASIN のリストを取得する。

な感じで ok でしょうってことで。

実際やってみると、いくつか難関があって、

  • WebBrowser で接続するときに User-Agent を偽装しないといけない。
  • WebBrowser 出 iframe 内ので HTML をどうやって取るのか?

というところです。本来はWindows ストアアプリとして作りたいところですが、WinRT の WebView と使うとまともに動かないので、WPF の WebBrowser を使います。HttpClient で直接データを持ってきてもよいのですが、購入リスト自体が iframe になっているのと、HTML のパースが面倒なのでパスします。そのうち、HTML のパーサーを作って試してみましょう。

■WebBrowser で User-Agent を偽装する

何故、偽装が必要かというと、そのまま WebBrowser で呼び出したときには、

こんな画面が出て接続ができません。そんな訳で、User-Agent を偽装しています。

private void OnClickCheck(object sender, RoutedEventArgs e)
{
    // url を取得
    string url = "http://read.amazon.co.jp" + "?dummy=" + DateTime.Now.Ticks.ToString();
    string ua = "User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko"; // IE
    wb.Navigate(new Uri(url), null, null, ua);
}

偽装自体は簡単で、Navigate メソッドの第4引数に設定します。ここにヘッダを追加できるので、複数追加したいときは “rn” で追加すればよいそうです。すると、初回だけ Kindle のログイン画面が出て、その後はブラウザの cookie(かな?)が使われます。
URL に dummy を渡しているのはキャッシュが使われないようにするためです。

■購入リストを取得する

購入リストは iframe の中にあります。WebBrowser.Document が mshtml.HTMLDocument にあたるので、これを使って DOM で解析していきます。mshtml 自体は、参照設定で「アセンブリ」→「拡張」にある「Microsoft.mshtml」をチェックします。

結構、手間取ったのですが、iframe の中身を取るためには、doc.frames.item(0) な感じで iframe を取ってきて、mshtml.HTMLWindow2 で iframe のウィンドウを取得します。その後は、再び document プロパティで解析するというパターンですね。

private void OnClickGetList(object sender, RoutedEventArgs e)
{
    var doc = wb.Document as mshtml.HTMLDocument;
    var iwin = doc.frames.item(0) as mshtml.HTMLWindow2;
    var idoc = iwin.document as mshtml.HTMLDocument;
    var titles = idoc.getElementById("titles_inner_wrapper");

    vm.Books.Clear();
    var books = vm.Books;
    foreach (mshtml.IHTMLElement it in titles.children)
    {
        var id = it.getAttribute("id") as string;
        books.Add(GetBook(it));
        Debug.WriteLine(id);
    }
}

上記の方法は、WPF の WebBrowser でやった方法ですが、Windows デスクトップアプリの WebBrowser では次のようになります。既に、HtmlDocument が定義されているのでそのまま使います。iframe に対しては、GetElementById を使っては取れないので、Frames コレクションから直接取ってきます。

private void button2_Click(object sender, EventArgs e)
{
    var doc = webBrowser1.Document;
    // var iframe = doc.GetElementById("KindleLibraryIFrame");
    var iwin = doc.Window.Frames[0];
    var idoc = iwin.Document;
    var titles = idoc.GetElementById("titles_inner_wrapper");
    var ids = new List<string>();
    foreach ( HtmlElement it in titles.Children ) {
        ids.Add(it.Id);
        Debug.WriteLine(it.Id);
    }
}

購入一覧の場合は、最初の frame にあるので無条件に 0 を指定していますが、複数の iframe がある場合はチェックが必要でしょう。

こうすることで、WebBrowser を使って Kindle の購入一覧を取得できます。

お次は、取得した履歴をどうやってストアアプリと連携させるか、のトリックを紹介しましょう。

カテゴリー: C#, WPF | [WinRT] Kindle の購入リストを取得する はコメントを受け付けていません

[WinRT] ストアアプリのセカンダリタイルを動的に作成する方法

Kindle Launcher の技術ネタの続きです。

クイック スタート: セカンダリ タイルのピン留め (XAML) (Windows)
http://msdn.microsoft.com/ja-jp/library/windows/apps/xaml/hh868249.aspx
WinRT/Metro TIPS:Windows 8.1の新機能、セカンダリ・タイル作成時の代替ロゴを使うには?[Windows 8.1ストア・アプリ開発] – @IT
http://www.atmarkit.co.jp/ait/articles/1312/05/news084.html

ストアアプリを登録すると、スタート画面いタイルを表示できますが、これを複数表示させることができます。複数のタイルにIDを付けておいて、いろいろな状態でアプリを起動することが可能です。
サンプルには、リソース「ms-appx:///」のタイルを表示する方法が載っていますが、実は「ms-appdata:///」も使えます(とマニュアルに書いてあります)。リソースの場合はアプリ内に含める必要があるのですが、アプリケーションデータ(ms-appdata)が使えるということは、どこからダウンロードしたり作成した画像を使えるということです。
これを Amazon の書籍の画像を使えば、スタート画面に本の画像が表示できるということです。

アプリケーションデータの扱いについては、以下を立ち読みするか、

ひと目でわかる Windowsストアアプリ開発入門VisualC#2012編
http://www.amazon.co.jp//dp/482229806X

マニュアルのほうはこっちで。

Windows ランタイムを使ったアプリ データへのアクセス (Windows ランタイム アプリ) – Windows app development
http://msdn.microsoft.com/ja-jp/library/windows/apps/hh464917.aspx

を参照してください。

動的にセカンダリタイルを作る

Kindle Launcher で使っているのがこれです。セカンダリタイルは、いちいちユーザーの確認が必要なので、一気に作ることがでいません。まあ、プログラムで一気に作るとスタート画面が「D○c○m○」で埋もれてしまいそうなので、これはこれでいいのかもしれませんが。

private async void OnButtonMakeTile(object sender, RoutedEventArgs e)
{
    var item = this.itemGridView.SelectedItem as Book;
    if (item == null) return;

    await CreateSecondaryTileAsync(item);
}

private async System.Threading.Tasks.Task<bool> CreateSecondaryTileAsync(Book item)
{
    string tileId = "ASIN-" + item.ASIN;
    var tile = new Windows.UI.StartScreen.SecondaryTile()
    {
        TileId = tileId,
        DisplayName = item.Title,
        Arguments = item.ASIN,
        RoamingEnabled = true,
    };
    tile.VisualElements.ForegroundText = Windows.UI.StartScreen.ForegroundText.Light;
    tile.VisualElements.ShowNameOnSquare150x150Logo = true;

    // アプリローカルに保存
    var cl = new HttpClient();
    var data = await cl.GetByteArrayAsync(new Uri(item.IconUrl));

    var folder = Windows.Storage.ApplicationData.Current.LocalFolder;
    var path = item.ASIN + ".jpg";

    try
    {
        var file = await folder.CreateFileAsync(path);
        using (var sw = await file.OpenStreamForWriteAsync())
        {
            sw.Write(data, 0, data.Length);
            sw.Flush();
        }
    }
    catch
    {
        // 同名のファイルがある場合は、そのまま使う
    }

    // tile.VisualElements.Square150x150Logo = new Uri(item.IconUrl);
    tile.VisualElements.Square150x150Logo = new Uri("ms-appdata:///local/" + path );

    return await tile.RequestCreateForSelectionAsync(GetElementRect( this.btnMakeTile ));
}

public static Rect GetElementRect(FrameworkElement element)
{
    GeneralTransform buttonTransform = element.TransformToVisual(null);
    Point point = buttonTransform.TransformPoint(new Point());
    return new Rect(point, new Size(element.ActualWidth, element.ActualHeight));
}

Kindle の商品画像を HttpClient#GetByteArrayAsync でダウンロードしてきて、アプリケーションデータに保存します。保存するファイル名が一意になるようにするため、ASIN コードそのものを使っています。
保存したあとに、セカンダリタイルの画像を

tile.VisualElements.Square150x150Logo = new Uri("ms-appdata:///local/" + path );

のように指定しておけば良いわけです。

起動時に分岐させる

セカンダリタイルをクリックしたときの引数は TileId に設定しておきます。ここでは「”ASIN-” + item.ASIN」のように設定しています。これが引数となってアプリの起動時にわたるので、app.xaml.cs を開いて、以下のように、e.Arguments で判別させます。

protected async override void OnLaunched(LaunchActivatedEventArgs e)
{
#if DEBUG
    if (System.Diagnostics.Debugger.IsAttached)
    {
        this.DebugSettings.EnableFrameRateCounter = true;
    }
#endif

    if (e.Arguments != "")
    {
        var asin = e.Arguments;
        // 引数がある場合は、Kindle を起動する
        var opt = new LauncherOptions();
#if !WINDOWS_PHONE_APP
        opt.DesiredRemainingView = Windows.UI.ViewManagement.ViewSizePreference.UseNone;
#endif
        await Launcher.LaunchUriAsync(
            new Uri(string.Format("http://read.amazon.co.jp/?asin={0}", asin)), opt );
    }

引数は ASIN コードなので、そのまま、Kindle Cloud Reader に渡せば ok です。LauncherOptions を指定しておけば、起動時の状態が設定できるので Kindle Cloud Reader が全面にできるように UseNone に設定しておきます。デフォルトは半分半分ですね。

タイル自体は、tile.VisualElements で色々と外観が変えられますが、ここでは表紙を全面出しているため書名がちょっと見づらくなっています。まあ、ダウンロード時に画像の加工をして保存するとか、いろいろと手段があります。画像の加工自体は、

[win8.1] RenderTargetBitmapクラスで、XAMLをビットマップ形式で保存する
http://www.moonmile.net/blog/archives/4983

こんな風に RenderTargetBitmap を使えば手軽にできるので、一度、画面に表示させたうえで保存すればよいでしょう。ピン留めする直前とかに作れば ok かと。
あと、画像の入れ替えやバッチなども作れるので、既読とか交互に宣伝とかを流すのもよいでしょう。

カテゴリー: C#, WinRT | [WinRT] ストアアプリのセカンダリタイルを動的に作成する方法 はコメントを受け付けていません

[WinRT] ストアアプリで Amazon の商品検索をする方法

Kindle Launcher を作るときに、Amazon から商品検索をしたかったので、これに嵌りました。サンプルコード自体は Amazon で提供されているものの、WinRT(Windows ストアアプリ)では動かなかったんですよね。

■アフリエイトキーを取得する

http://affiliate.amazon.co.jp/

上記からアフリエイトキーを作成します。AWS とは違うので注意が必要(暫く使っていなかったので探した探した)。まあ、商品検索 API を使いたいのにクレジットカード登録とかは変な話なので。

API キーの取得等は以下を参照してください。アプリでは「アクセスキー」と「シークレットキー」の2つを使います。

Access Key IDとSecret Access Keyの取得 – Amazon Web サービス
http://www.ajaxtower.jp/ecs/pre/index1.html

■SignedRequestHelper.cs を修正する

C# では、アフリエイト API を使うときにヘルパークラスを使うと便利です。WPF や Windows Phone の場合は、提供されている SignedRequestHelper のままでいいのですが、WinRT の場合はちょっと違います。

[C#] Amazon Product Advertising API の利用(REST サンプルコードの修正) | プログラミング生放送
http://pronama.azurewebsites.net/2014/05/23/csharp-amazon-product-advertising-api-rest-sample-code-modified/

↓こんな風に修正してください。

SignedRequestHelper.cs for WinRT
https://gist.github.com/moonmile/16c950174d0c276cc2c3

System.Security.Cryptography じゃなくて、Windows.Security.Cryptography.Core を使うってのがミソですね。下に元ネタがあります。

c# – Working with hmacsha256 in windows store app – Stack Overflow
http://stackoverflow.com/questions/13293420/working-with-hmacsha256-in-windows-store-app

SHA で MAC キーを使うところが少し違うだけで、ヘルパークラスの使い方は変わりません。
おそらく、WinRT での違いがあって、ストア版の Amazon アプリが増えないのかもしれません。セキュリティ回りの名前空間とクラスが再整理(改悪?)されているので、いままでのコードが使えないのが面倒です。互換クラスを作ってしまえばよいような気もするのですが。

■指定したキーワードで商品情報を検索する

SignedRequestHelper を使って Amazon の商品を検索しているところがこんな感じです。

private const string MY_AWS_ACCESS_KEY_ID = "YOUR_AWS_ACCESS_KEY_ID";
private const string MY_AWS_SECRET_KEY = "YOUR_AWS_SECRET_KEY";
private const string DESTINATION = "ecs.amazonaws.jp";

/// <summary>
/// 検索ボタンをクリック
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void OnButtonItemSearch(object sender, RoutedEventArgs e)
{
    var helper = new SignedRequestHelper(MY_AWS_ACCESS_KEY_ID, MY_AWS_SECRET_KEY, DESTINATION, "mycompany-22");
    var param = new Dictionary<string, String>();
    param["Service"] = "AWSECommerceService";
    param["Version"] = "2011-08-01";
    param["Operation"] = "ItemSearch";
    param["Keywords"] = this.vm.Title + " Kindle";
    param["Author"] = this.vm.Author;
    param["SearchIndex"] = "All"; // "Books";
    param["ResponseGroup"] = "Large";

    try
    {
        var requestUrl = helper.Sign(param);
        var request = new System.Net.Http.HttpClient();
        var res = await request.GetStringAsync(new Uri(requestUrl));
        // Debug.WriteLine(res);

        var doc = ExDocument.LoadXml(res);
        int TotalPages = doc * "TotalPages";
        int TotalResults = doc * "TotalResults";
        vm.Total = TotalResults;

        int count = 0;
        vm.Books.Clear();

        for (int i = 1; i <= TotalPages; i++)
        {
            param["ItemPage"] = i.ToString();

            await Task.Delay(1000); // ちょっとだけ待つ
            var reqUrl = helper.Sign(param);
            res = await request.GetStringAsync(new Uri(reqUrl));
            doc = ExDocument.LoadXml(res);
            var ASINs = doc * "Item" * "ASIN";
            foreach (var it in ASINs)
            {
                count++;
                var asin = it.Value;
                var item = it.Parent;
                var format = item * "ItemAttributes" * "Format";

                if (format == "Kindle本")
                {
                    string title = item * "ItemAttributes" * "Title";
                    string iconurl = item * "LargeImage" * "URL";
                    Debug.WriteLine("{0} ASIN:{1} Title:{2} img:{3} ", count, asin, title, iconurl);

                    vm.Books.Add(new Book { ASIN = asin, Title = title, IconUrl = iconurl });
                    vm.TotalKindle = vm.Books.Count();
                    vm.TotalStr = string.Format("{0}/{1}", vm.TotalKindle, vm.Total);
                }
            }
        }
    }
    catch (Exception ex)
    {
        // MessageBox.Show("API制限に達しました。暫く経ってから実行してくださいn" + ex.Message);
        Debug.WriteLine(ex.Message);
    }
}

ItemSearch で戻される検索はページ単位で返ってくるのと、検索数が制限されています。サイトでは結構な数で検索できるのですが、API の場合は、4000件(400ページ)程度のようです。
この検索では、商品画像(本の表紙)を取るために Large にして商品データを取っています。まあ、Kindle Launcher の書籍検索がちょっと遅いのはそれが原因でもあります。ResponseGroup を変更するともうちょっとデータ量を絞れるでしょう。
SearchIndex を Books だけにすると「和書」だけになるので、All にしています。洋書も検索結果に入れたかったのです。Keywords に Kindle を含めているのは「Kindle本」に絞るためです。こうすると、おそらく Kindle 対象の本だけが検索できるはずです。そして、もういちど取得したデータを改めて」「Kindle本」で検索しています。

データは XML 形式で取得できるので適当にパースします。ここでは自前の ExDoc を使ってパースをしています。

https://github.com/moonmile/ExDoc/

にあるのは、Windows デスクトップ版なので、近いうちに PCL 版をアップしておきます。

XML データからクラスを作成する VS のアドイン(だっけ?)もあるので、それを使うのもよいでしょう。

■検索した商品を表示する

表示自体は GridView でやっています。
そのままのテンプレートでは使いづらいので「GridView.ItemTemplate」で自前テンプレートを作ります。

<!-- 水平スクロール グリッド -->
<GridView
    Grid.Column="1" Grid.Row="1"
    x:Name="itemGridView"
    ItemsSource="{Binding Books}"
    TabIndex="1"
    Padding="10"
    SelectionMode="{Binding SelectMode}"
    IsSwipeEnabled="false"
    Tapped="itemGridView_Tapped">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid HorizontalAlignment="Left" Width="250" Height="250">
                <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
                    <Image Source="{Binding IconUrl}" Stretch="UniformToFill" />
                </Border>
                <StackPanel VerticalAlignment="Bottom" Orientation="Vertical"
                    Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                    <TextBlock Text="{Binding ASIN}"
                        Height="18"
                        Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}"
                        TextWrapping="NoWrap" Margin="3"/>
                    <TextBlock Text="{Binding Title}"
                        Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}"
                        Height="30" Margin="3"
                        TextWrapping="NoWrap"/>
                </StackPanel>
            </Grid>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

IconUrl とか ASIN とかをバインドしておけば、自動的に表示されますね。

こんな風に表示されます。

カテゴリー: C#, WinRT | [WinRT] ストアアプリで Amazon の商品検索をする方法 はコメントを受け付けていません

セカンドディスプレイのタッチを有効に働かせる方法

先日、液晶テレビを買ったので、家族用のタッチ液晶 LG-23ET83 が不要になりました。液晶テレビに PC がつなげられるので、(ちょっと字は滲みますが)ブラウザも快適に動いています。そうそう、Mac mini をつなげて字の滲みを比較する予定なのですが、まだやっていません。

さて、余ったタッチ液晶を仕事用のディスプレイにしようと思って、3面にしました。

image

メインではなくて、セカンドディスプレイにしてあるのは、タッチパネルためか映り込みが激しいんですよね、このディスプレイ。映像を映しているときにはいいのですが、面と向かってプログラミングをすると自分の顔を見ながら仕事する、ってことになってちょっと変です。なので、通常は、左のディスプレイで作業をして、真ん中をタッチにしようと考えたわけです。

■メインディスプレイにタッチが移動する?

この順番でつなげると、何故か、タッチ用の2番のディスプレイを触っているにも関わらず、1番目の液晶のマウスカーソルが動きます。どうやら、タッチ液晶の反応を別のディスプレイ(メインディスプレイ)に送っているらしい現象なんですよね。

仕方がないので、2番目のタッチ液晶の「これをメインディスプレイにする」にして作業をしていました。何故か、メインディスプレイに設定すると、タッチ部分と液晶表示は同期するのです。これでもまあいいのですが、タスクバーの時計が表示されているところが、メインのタスクバーだけなので、ちょっと変な感じなんですよ。あと、デスクトップにアイコンを作るときはメインディスプレイに作成されるので、いちいち2番目のディスプレイ(タッチ液晶)に探しに行かないといけません。

非常に面倒だった…のですが、解決策がありました。

■タブレットPC設定で、タッチ液晶を設定する

コントロールパネルで「タッチ」を検索して、「タブレットPC設定」をクリックします。

image

構成にある「ペンとタッチディスプレイを構成します」の「セットアップ」ボタンをクリックすると、どれがタッチ液晶なのかという設定ができます。

image

タッチ入力を選択すると、全画面にこんな風な表示がでます。image

これを、タッチ液晶のところまで Enter キーで動かし後、タッチ。設定を保存すれば、メインディスプレイでなくてもタッチ液晶が正しく認識されます。

タッチ液晶が2枚あったときにはどうなるのか?は不明ですが、ひとまず1枚のときはこれで良さそうです。

カテゴリー: windows 8.1 | セカンドディスプレイのタッチを有効に働かせる方法 はコメントを受け付けていません

本ごとに Kindle Cloud Reader へのリンクをスタート画面に作る方法

ブラウザで Kindle 本を読む方法とスタート画面へのピン留め | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/6311

の続きです。

IE からショートカットを作ってスタート画面にタイル表示できることは分かったのですが、どうせならば、本のアイコンが表示されて欲しいですよね。タイトルが変えられるとはいえ、すべて「k」になってしまうのでは、どれがどの本か分からなくなってしまいます。

image

が、少し手間を掛けるとアイコンの登録もできます。

■URLショートカットを作成する

[InternetShortcut]
IDList=
URL=http://read.amazon.co.jp/?asin=<asin>
IconFile=http://images-jp.amazon.com/images/P/<asin>?.09.MZZZZZZZ
IconIndex=1

 

メモ帳などで、<タイトル>.url というファイルを作成します。これは、IE9 とは違って、昔のURLショートカットでブラウザで表示する URL とアイコンを指定しています。<asin>なところは、Kindle 本の ASIN コードを入れます。IconFile 自体は、amazon の商品画像を指定しています。

IE9 で作るショートカットは、Web サイトをピン留めする方法 にある通り website ショートカットなんですね。なるほど。開くと何故か IconFile が書き換わるので、スタート画面のアイコンが変更されてしまうという仕様のようです。なので、*.website ファイル自体を読み取り専用にすれば同じことができます。

作成したショートを プログラムの中に移動させます。

image

このフォルダは Win+R で「shell:programs」で開けます。私の場合は Kindle というフォルダを作ってその中にいれています。

ここにコピーすると、スタート画面で「新しいプログラムが…」が出るので、ちまちまとスタート画面にピン留めをすればできあがり。ピン留自体と順番を変えるのが非常に面倒なのですが、まあ、

アイコン自体が、デスクトップ用の小さな画像になってしまうので、視認性が悪いのが難点。登録自体は、Amazon Web Service を利用すればいいので、さほど難しくないでしょう。たぶん、Excel VBA とかを使ってもできそう。

スタート画面のタイル自体のカスタマイズは、以下でできるのでもう少しなんとかなりそう。

デスクトップ アプリのスタート画面のタイルをカスタマイズする方法 (Windows ランタイム アプリ) (Windows)
http://msdn.microsoft.com/ja-jp/library/windows/apps/xaml/dn449733.aspx

他にも、仮のストアアプリを登録しておいてサブタイルでもできるでしょう。まあ、漫画本ばっかりずらずらと並ぶのもアレなのでカテゴリごとのランチャーアプリを作るほうが良いかも。大量に URL ショートカットを作るのも面倒だし、シリーズ毎に分けられると積読が少なくなるでしょう。あとは参考用の洋書まわりとか。

カテゴリー: 雑談 | 本ごとに Kindle Cloud Reader へのリンクをスタート画面に作る方法 はコメントを受け付けていません

ブラウザで Kindle 本を読む方法とスタート画面へのピン留め

Windows で Kindle を読む方法と言えば、日本語Windows 8 Pro で Kindle を動かす方法 な感じで、Android のエミュレータ Bluestacks などを使う方法しかなかったのですが、先日 amazon.co.jp から Kindle Cloud Reader が出ました。

アマゾン、PC ブラウザで読めるKindle Cloud Reader 提供開始。漫画・雑誌と洋書のみ – Engadget Japanese
http://japanese.engadget.com/2014/09/22/pc-kindle-cloud-reader/

記事では「漫画と洋書のみ」となっていますが、画像キャプチャの雑誌系や、いくつかの横書きの日本語本は読めます。順次、コンバートということなので、そのうち対応されるかもしれません(縦書きが面倒なので対応されない、というパターンもありますが)

image

多少画面は荒いですが、漫画が読めるのと洋書が PC で読めるのは便利です。手元にあるのは、Kindle whitepaper と iPad なので、普段は Kindle、ちょっと大きめなもので読むときは iPad を使っています。

image

液晶ディスプレイで並列に表示しながら使えるのは結構便利ですね。これはデスクトップ版の IE で表示してますが、Windows ストア アプリアプリ版のモダンIEのほうでも表示ができます。

■スタート画面に指定の本をピン留めする

いくつか手順…と制限がありますが、スタート画面に Kindle 本をピン留めできます。

 

多少、面倒臭い(苦笑)のは後でツール化するとして、http://read.amazon.co.jp/?asin=<ASIN> の形式で指定の本を開くことができます。この asin は amazon の商品コードで一意に決まっているものです。
Kindle 本を開いたときに、登録情報の ASIN を見ると、「B00AA9W658」のようなコードがあります。

image

これを http://read.amazon.co.jp/?asin=B00AA9W658 のようにすれば、「ヨコハマ買い出し紀行」の1巻が Cloud reader で開かれます。購入していない場合は、左下に「完全版を入手」と出るので、サンプルを開いていることがわかりますね。ちなみに、私は紙のほうを持っているので Kindle のほうは買ってません。

image

さて、購入した本の場合、これをいちいちブラウザで打つのは面倒なのでスタート画面にピン留めします。IE の左上のアイコンをデスクトップへドラッグするとショートカットができます。

image

このショートカットをメモ帳で開くと、こんな感じに URL が指定されています。

image

適当にタイトルを変えておけばよいでしょう。

image

ここに ASIN が書かれているので、これを書き換えれば、指定の Kindle 本を呼び出すことができます。また amazon の登録情報の表示以外にも URL にも ASIN が含まれているので、そこから抜き出すことも可能です。http://www.amazon.co.jp/%E3%83%A8%E3%82%B3%E3%83%8F%E3%83%9E%E8%B2%B7%E3%81%84%E5%87%BA%E3%81%97%E7%B4%80%E8%A1%8C%EF%BC%88%EF%BC%91%EF%BC%89-%E3%82%A2%E3%83%95%E3%82%BF%E3%83%8C%E3%83%BC%E3%83%B3KC-%E8%8A%A6%E5%A5%88%E9%87%8E%E3%81%B2%E3%81%A8%E3%81%97-ebook/dp/B00AA9W658/ref=sr_1_1?s=digital-text&ie=UTF8&qid=1411541708&sr=1-1&keywords=%E3%83%A8%E3%82%B3%E3%83%8F%E3%83%9E%E8%B2%B7%E3%81%84%E5%87%BA%E3%81%97%E7%B4%80%E8%A1%8C この中の dp/ の後ろにあるコードが ASIN なので、これを抜き出します。

ショートカット自体を、スタート画面にピン留めするには、いくつか方法がありますが、次のように作れます。

  1. Win+R で、shell:programs と入力して、スタートメニュー/プログラムのフォルダを表示する。
  2. Kindle のようなフォルダを作って、作成したショートカットをコピーする。
  3. スタート画面に「新しいアプリがインストールされました」と出るので、アプリ画面から、指定のアイコンをスタート画面へピン留めする。

image

こうすると、アイコンは変わりませんが、スタート画面から直接 Kindle 本を起動できます。

image

まあ、欠点は

  • (おそらく)デフォルトのブラウザが起動されるので、モダンIE に設定しておかないと駄目?
  • アイコンが変更できない。

ってところです。ただ、Kindle にたくさんの本がある場合はちょっと便利かと。特に漫画のシリーズ本なんかはたくさんの本が並ぶので、1冊ごとにアイコンがあると便利…かもしれません。

アイコン自体は IconFile で指定されているのですが、これを変更してもアイコンは変わりません。と言いますか、元の Kindle 以外のアイコンはうまく出ません。ライブラリごとに本をまとめたりすると便利そうなので、別途簡易アプリを作るほうがいいかもしれませんね。

ちなみに Kindle では送り先の端末が制限されていますが、この Kindle Cloud Reader は制限の内に入っていないようです。そのうち、制限されるような気がしないでもないのですが、ブラウザでよんでいる限り、何台の PC でも利用できそうです。英語版の Kindle on PC はどうだったのかな?

一応、Kindle Cloud Reader も送り先に出てくるので、これで1台カウントみたいですね。

image

ちなみに初代 Surface RT でも動いています。横向きだとロードがかかって重たいですが、縦置きだとサクサク進みます。

埋め込み画像への固定リンク

カテゴリー: 雑談 | ブラウザで Kindle 本を読む方法とスタート画面へのピン留め はコメントを受け付けていません

CloudFlash を無線LANルータに接続して使う(ネットワーク参加させる)

CloudFlash をデジカメではない環境で動かそう | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/6277

の続きです、CloudFlash は WiFi のホスト機能を持っていてデフォルトで SSID が「Cloud Flash」にして接続ができます。多少チープな環境ですが、写真をダウンロードできる web サーバを積んでいるので、ブラウザから写真がダウンロードできて便利です。
普通は、デジカメに CloudFlash を差して使うところですが、私の場合は SD カードをリーダを使うことで「単体」で CloudFlash を動かします。…意味があるのかどうかは不明ですが(苦笑)、ひとまず、

  • 特別な機器なし(カードリーダーは必要だけど)に WiFi 付きの Linux 環境が使える。
  • ストレージは SD メモリで拡張できるので、うまくやれば .NET が動く?
  • 初代 Surface からターミナルで接続しよう

ってのが目標です。

■CloudFlash を無線LAN 環境に参加させる

デフォルトでは、CloudFlash がホストになっていますが、これだといちいちノートPCやスマートフォンの接続先を変更しないので少々面倒です(逆に外にいるときは便利なんですけど)。なので、既存のネットワークに参加させるように設定し直します。

設定自体は、日経Linuxの2014/08号を参考にするか、以下の記事を読み進めます。

Flucard Pro – Wi-Fi 機能搭載 SD カードを Linux サーバーとして楽しむ方法
http://netbuffalo.doorblog.jp/archives/4811269.html

普通はネットワークに接続するだけでは意味がないので、自動起動時にスクリプトを動かすようにするのですが、単純に WiFi させるだけならば以下のスクリプトを autorun.sh にして SD メモリのルートに置きます。

– autorun.sh
– APP/wifi.conf
– APP/wpa.conf

#!/bin/sh

APP_HOME=/mnt/sd/APP
LOG=$APP_HOME/log.txt
SCAN=$APP_HOME/scan.txt

# echo "---" >> $LOG
# date       >> $LOG
# ifconfig   >> $LOG

. $APP_HOME/wifi.conf
ifconfig mlan0 down
sleep 3
a1 
sleep 3
ifconfig mlan0 up || exit 1
sleep 3

wpa_supplicant -i mlan0 -B -c $APP_HOME/$WPA_CONF
ifconfig mlan0 $IPADDR netmask $NETMASK  
route add default gw $GATEWAY  

sync
exit 0

起動時の動作は適宜 APP/log.txt に書き込んでいるのですが、Windows のドライブとの同期がうまく取れないときがあります。そのような時は、一度 SD カードを抜き差しして再度確認してください。一応 sync で同期させているのですが、完全ではないようです。

ESSID_NAME=<接続先のSSID>
WPA_CONF=wpa.conf
BOOTPROTO=static
IPADDR=172.16.0.99
NETMASK=255.255.255.0
GATEWAY=172.16.0.1
network={
	ssid="<接続先のSSID>"
	key_mgmt=WPA-PSK
	proto=WPA
	pairwise=TKIP
	group=TKIP
	#psk="<password>"
	psk=ff2ba81ca3ce58f606fd99998cf54271d3af00f36b371b235b7f53c0438e51a8
}

ひとまず固定IPにしてネットワークに参加させます。wifi.conf と wpa.conf の書き方は Linux 系のネットワークを調べるとよいでしょう。通常の Linux のものがそのまま使えます。

どうやら、autorun.sh スクリプト内にある a1 スクリプトが肝で、カーネルライブラリをロードしているようです。他にも w1 というスクリプトがあるので、どちらかを使えばいいんでしょう。

# cat a1
ifconfig mlan0 down

insmod /lib/ar6000.ko
sleep 1
insmod /lib/ka2000-sdio.ko
sleep 1
#iwconfig mlan0 txpower 8
#ifconfig mlan0 up
#dev=ath0
dev=mlan0

#iwpriv $dev version
#iwpriv $dev httxcfg 0x62
#iwpriv $dev htcapinfo 0x1820000

# cat w1
#insmod /mnt/sd/ka2000-sdio.ko
insmod /lib/ka2000-sdio.ko

#iwpriv mlan0 version
#iwpriv mlan0 httxcfg 0x62
#iwpriv mlan0 htcapinfo 0x1820000
#

こうすると、無線ルータにネットワーク参加して「172.16.0.99」で接続ができます。CloudFlash 自体は telnetd が自動で起動されるようになっているので(スクリプト自体は、/etc/init.d 内にあります)、tera term などで接続が可能です。ただし、ユーザー名、パスワードがない状態なので、社内 LAN などの公共のものに接続する場合は注意してください。中身が覗けてしまいますから。

■busybox が使われている

CloudFlash のメモリは 30MB弱しかありません。

組み込み Linux & 簡易 Web サーバーとして動くので、まあこれで十分といえば十分です。コマンド自体は組み込み用の busybox が使われているので、うまくマウントしてやれば機能を増やすことができます。SD カードのストレージ自体はたくさんありますから。
ストレージを活用して、Ruby を入れている例もあるので、ひょっとすると .NET Framework が乗るかもしれません。ARM で動いているそうなので、Raspberry Pi でビルドしたものを持ってくるか、QEMU 環境でビルドしたものをコピーすればよいでしょう。

■ファイル等は Windows からコピーできる

autorun.sh 等のファイルは、Windows PC からコピーできます。普通に SD メモリカードを USB リーダーに差し込んで PC に接続すればドライブとして認識がされます。

CloudFlash では /mnt/sd の環境が、SD メモリのルートになります。ここに適当にフォルダやファイルをコピーして起動時に、ln -s 等してやれば Ruby などを動かすことができます。CloudFlash の中身自体は ROM になっているので、起動時にこの作業が必ず必要になります。

ちなみに date 自体は起動時に 2012-1-1 にリセットされているようで、日付をチェックするにはちょっと特殊な操作が必要かもしれません。

また、ファイル自体は FAT32 フォーマットなので、Linux 特有のグループ設定などはできないと思われます。

■元の WiFi 環境に戻すにはどうするのか?

ブラウザを使って Auto WiFi を OFF にすることはできたのですが、元の ON に戻すには(CloudFlash を WiFi のホストとして機能させるには)どうしたらいいのかと悩みました。何故か、どこにも書いていない。
と思ったら、実は簡単です。

SD メモリカードをクイックフォーマットすれば OK です。

初期状態では、WiFi のホストとして機能しているので、確かにそうだった。結構悩んで、元に戻す設定を3時間ほど探していました。フォーマットすると JPEG などの写真データも一緒に消えてしまうので(フォルダ等が残っている状態でも駄目なようです)、きれいさっぱりフォーマットします。どうやらクイックフォーマットでよさそうです。実験としてはホスト用とネットワーク参加用の2種類のメモリカードを用意しておけばよいでしょう。
CloudFlash 自体のリセットは、SD カードリーダーに抜き差しすることで可能です。10秒ぐらいまてばいいみたいですね。

カテゴリー: 開発 | CloudFlash を無線LANルータに接続して使う(ネットワーク参加させる) はコメントを受け付けていません