C# Programming

Image

簡単なモバイルアプリケーション開発4
現在の緯度、経度の取得

開発環境: Visual Studio 2005 

1.目次

1.目次
2.目的
3.参考書
4.作り方
5.ダウンロード
6.ソースコード
7.まとめ

2.目的

W-Zero3で、基地局の位置情報(緯度、経度、郵便番号)を取得できます。ここでは、SerialPortを使った、W-Zero3の位置情報取得プログラミングについて説明します。

この位置情報がわかれば、郵便番号→住所、緯度経度→地図、天気など、いろいろな応用が可能です。

3.参考書

  1. C#研究室.Live Space / W-ZERO3
  2. MSDN .NET Compact Framework 向けの表示方向切り替え対応および高dpi対応アプリケーションの開発
  3. 機械の体
  4. w-zero3日和
  5. W-ZERO3tool

4.作り方

 

4.1 WZERO3 での位置情報の取得

W-Zero3 での位置情報の取得方法は、参考文献(3)、(4)、(5)あたりに書いてあります。普通、バイナリエディタで、のぞくか?のぞいたとしても、見つけるかぁ?とにかく、偉大な先人たちに感謝。

いくつかバリエーションはあるようですが、シリアルポートに次のコマンドを入れてあげると、位置情報が帰ってきます。

AT@LBC1
AT@LBC?
AT@LBC2

.NET Framework 2.0 からは、シリアルポートが追加されており、シリアルポートへのアクセスがとても楽になっていますので、割と簡単に位置情報を取得することができます。

ただし、W-Zero3の位置情報取得のためのコマンドも、シリアルポートの通信条件も正式情報が公開されておらず、試行錯誤の結果をもとにして実装していますので、パラーメータは怪しいです。SHARP/WILLCOMから、このような開発のための技術情報をノンサポートでかまわないので、出してもらいたいです。メーカーの方、よろしくお願いします。

4.2 基本動作

基本的な動作は次のようになります。

  1. SerialPortインスタンスを作る。
    "COM1", 9600bps, 8bitノンパリを指定します。各種プロパティをセットします。ここで、ハンドシェークはRequestToSend、DtrEnabled = trueです。Read/Writeタイムアウトは1秒にしました。
  2. イベントハンドラの処理
    DataReceived, PinChanged, Error Received, Disposedのイベントハンドラを追加します。Error Received, PinChangedは、あまり意味のあるデータが帰ってきませんでした。DataReceived イベントは、データがレディになった段階で呼び出してくれる非同期動作になります。このイベントハンドらが呼ばれたら、ReadExisting()によりバッファに格納されているデータを読み込みに行きます。したがって、呼び出し側は単にどんどんデータを送り出していけばよく、DataReceivedイベントハンドラでデータの呼び出しをしていきます。問題は、ReadExistingで不定長のデータが帰ってきますので、どの段階でデータがそろったかを判断する必要があります。このときの流れを調べてみると、次のようにSerialPort をCloseすると、Disposedイベントが呼ばれるので、Disposed イベントで読み取り完了と判断することにします。

    このときのデータのやり取りを図解すると次のようになります。

    Image
     
  3. 正しく位置データを取得できない場合がある

    正しく動作していれば、

    AT@LBC?
    1234567                <<<< 郵便番号
    N35:22:33       <<<<緯度
    E139:44:55             <<<<<経度
    OK
    AT@LBC2

    のような結果が返ってくると思います。しかし、なぜか、次のように、緯度や、経度や、郵便番号が正しく帰ってこない場合があります。これは、 参考文献(3)、(4)、(5)でも発生している共通の問題のようです。

    AT@LBC1
    OK
    AT@LBC?
    1234567
    -
    -
    OK
    AT@LBC2

    このため、経度、緯度、郵便番号が正しく取得できない時のエラー処理を行う必要があります。そこで、読み込んだ文字列を正規表現で解析し、エラーがある場合は例外を上げ るようにします。例外はForm側でキャッチして、Notificationでバルーンを表示するようにします。

    正しく読めている場合はプロパティに 値をセットします。
     

  4. その他の注意
    W-SIMの通信が行われている場合は、"COM1"データが取得できません。このため、"ポート 'COM1:' は存在しません。"というエラーメッセージが表示されます。

    対処方法は2つ考えられます。1つは、強制的に W-SIMの通信を切断し、位置情報を取得する方法。もう1つは、定期的に位置情報を取得し、最後に取得した位置情報を覚えておき、その値を使う方法です。ここでは、エラーにして、ユーザに判断させるようにします。
     

4.3 プロジェクトの作成

  1. プロジェクトの新規作成
    Visual Studioのメニューからプロジェクトの新規作成で、プロジェクトの種類で [スマートデバイス] → [デバイスアプリケーション] を選択します。
     
  2. ターゲットデバイスの設定
    エミュレータでも可能ですが、エミュレータではレスポンスが悪く、かったるいです。そこで、Windows Mobile 5.0 Pocket PC Deviceを指定して、実機上で開発します。
     
  3. Formの [FormFactor]プロパティ
    [Windows Mobile 5.0 Pocket PC VGA] を指定します。

4.4 WZero3Locatorクラスの実装

WZero3Locator.cs クラスを追加して、位置情報をシリアルポートから取得するクラスを実装します。WZero3Locatorクラスの実装は、6章を参照してください。

4.5 WZero3Locator呼び出し部分の実装

WZero3Locatorクラスは、次のようなパターンで使用します。

private void GetLocation()
{
    try
   
{
        WZero3Locator
w3loc = new WZero3Locator();
        w3loc.Received += SerialPortReceived;
        w3loc.GetLocation();
    }
    catch (Exception exc)
    {
    ....
    }
}

private void SerialPortReceived(object o, EventArgs e)
{
     textBox1.Text = (WZero3Locator)o.ZipCode;
     ....
}

4.6 コントロールの配置と結果の表示

WZero3Locator の表示方法は、いろいろあると思いますが、ここではListViewに追加するようにします。ListViewを追加して、メニューの "Get Location" を押したら、GetLocation()を呼び出すように実装します。結果は、SerialPortReceivedがコールバックされますので、そこでListViewのItemsに追加してあげればOKです。

Image

4.7 残りの作業

例によって、お決まりの次の作業を行えば、完成です。詳細は、参考文献(2)、(3)を参照してください。

  1. 縦型、横型レイアウトの調整
  2. メインメニューの実装
  3. アイコンの追加
  4. バージョン情報の表示
  5. インストーラの追加

5. ダウンロード

Version 1.0.0.0  2007/4/1 WZero3Locator.cab

6. ソースコード

主なソースコードです。

 

Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace Uchukamen.WZero3
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void menuItem2_Click(object sender, EventArgs e)
        {
            GetLocation();
        }

        private void GetLocation()
        {
            try
            {
                WZero3Locator w3loc = new WZero3Locator();
                w3loc.Received += SerialPortReceived;

                w3loc.GetLocation();
            }
            catch (Exception exc)
            {
                notification1.Caption = "WZero3Locator";
                notification1.Visible = true;
                notification1.Text = exc.Message;
            }
        }

        private void SerialPortReceived(object o, EventArgs e)
        {
            string[] list = {
                DateTime.Now.ToShortTimeString(),  
                ((WZero3Locator)o).ZipCode,
                ((WZero3Locator)o).Latitude,
                ((WZero3Locator)o).Longitude
            };

            listView1.Items.Add(new ListViewItem(list));
        }

        private void menuItem1_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
    }
}

 

WZero3Locator.cs
using System;
using System.IO.Ports;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Collections;
using System.Threading;

namespace Uchukamen.WZero3
{
    class WZero3Locator
    {
        private SerialPort serialPort = new 
            SerialPort("COM1", 9600, 
                System.IO.Ports.Parity.None, 8);

        public WZero3Locator()
        {
            serialPort.NewLine = "\r\n";
            serialPort.DtrEnable = true;
            serialPort.Handshake = Handshake.RequestToSend;
            serialPort.RtsEnable = true;
            serialPort.DataReceived += new 
                 SerialDataReceivedEventHandler
                     (serialPort_DataReceived);
            serialPort.PinChanged += new 
                 SerialPinChangedEventHandler
                     (serialPort_PinChanged);
            serialPort.ErrorReceived += new 
                 SerialErrorReceivedEventHandler
                     (serialPort_ErrorReceived);
            serialPort.Disposed += new EventHandler
                    (serialPort_Disposed);
            serialPort.ReadTimeout = 1000;
            serialPort.WriteTimeout = 1000;
        }

        public void Dispose()
        {
            serialPort.Dispose();
        }

        private void Send(string str)
        {
            while (serialPort.CtsHolding == true)
                Thread.Sleep(100);
            serialPort.WriteLine(str);

            Debug.WriteLine(">>>>>>>>>>>>:" + str);
        }

        public void GetLocation()
        {
            resultString = "";
            if (serialPort.IsOpen == false)
                serialPort.Open();

            Send("AT@LBC1");
            Send("AT@LBC?");
            Send("AT@LBC2");
            Send("AT");

            serialPort.Close();
        }

        private void serialPort_Disposed
               (object sender, EventArgs e)
        {
            Debug.WriteLine(resultString);
            ParseData(resultString);
            Received(this, null);
        }

        public EventHandler Received = null;

        private string resultString = "";

        private void serialPort_DataReceived
              (object sender, SerialDataReceivedEventArgs e)
        {
            string retStr = serialPort.ReadExisting();

            Debug.WriteLine("<<<<<<<<<<<<\n" + retStr);

            resultString += retStr;
        }

        private void serialPort_ErrorReceived
            (object sender, SerialErrorReceivedEventArgs e)
        {
            Debug.WriteLine("serialPort_ErrorReceived");
        }

        private void serialPort_PinChanged
            (object sender, SerialPinChangedEventArgs e)
        {
            Debug.WriteLine("serialPort_PinChanged: CTS=" 
               + serialPort.CtsHolding.ToString());
        }

        #region 郵便番号、緯度、経度のプロパティ
        private string errString = "";
        public string ErrString
        {
            get { return errString; }
        }

        private string zipCode;
        public string ZipCode
        {
            get { return zipCode; }
        }

        private string latitude;
        public string Latitude
        {
            get { return latitude; }
        }

        private string longitude;
        public string Longitude
        {
            get { return longitude; }
        }

        private float fLatitude;
        public float FLatitude
        {
            get { return fLatitude; }
        }

        private float fLongitude;
        public float FLongitude
        {
            get { return fLongitude; }
        }
        #endregion

        #region 郵便番号、緯度、経度の文字列処理
        public void ParseData(string str)
        {
            zipCode = GetZipCode(str);
            latitude = GetLatitude(str);
            longitude = GetLongitude(str);

            fLatitude = GetFloatLatitude(latitude);
            fLongitude = GetFloatLongitude(longitude);
        }

        private float GetFloatLatitude(string latitude)
        {
            Regex regVal = new Regex("[0-9]+");
            MatchCollection mcLatitude = 
                 regVal.Matches(latitude);
            return float.Parse(mcLatitude[0].Value)
                + (float.Parse(mcLatitude[1].Value)) / 60.0f
                + (float.Parse(mcLatitude[2].Value)) / 3600.0f;
        }

        private float GetFloatLongitude(string longitude)
        {
            Regex regVal = new Regex("[0-9]+");
            MatchCollection mcLongitude = 
                  regVal.Matches(longitude);
            return float.Parse(mcLongitude[0].Value)
                + float.Parse(mcLongitude[1].Value) / 60.0f
                + float.Parse(mcLongitude[2].Value) / 3600.0f;
        }

        private string GetZipCode(string str)
        {
            Regex regZip = new 
               Regex("[0-9][0-9][0-9][0-9][0-9][0-9][0-9]");
            Match match = regZip.Match(str);
            if (!match.Success)
                throw (new Exception("郵便番号の取得に失敗"));
            return match.Value;
        }

        private string GetLatitude(string str)
        {
            Regex regLat = new 
                 Regex("[NS][0-9]+:[0-9]+:[0-9]+");
            Match match = regLat.Match(str);
            if (!match.Success)
                throw (new Exception("緯度の取得に失敗"));
            return match.Value;
        }

        private string GetLongitude(string str)
        {
            Regex regLong = new 
                Regex("[EW][0-9]+:[0-9]+:[0-9]+");
            Match match = regLong.Match(str);
            if (!match.Success)
                throw (new Exception("緯度の取得に失敗"));
            return match.Value;
        }
        #endregion
    }
}

 

7. まとめ

さて、ここまでで、W-Zero3で緯度、経度情報から、現在位置の地図をWEBブラウザで表示できるのではないかと?思われたと思います。その通りです。.NET COMPACT FRAMEWORK 2.0では、Web Browserコントロールが標準で使用できます。したがって、あと数行実装するだけで、次のようなマップ表示までできてしまいます。

 

簡単なモバイルアプリケーション開発1〜4とみてきましたが、 あとは緯度、経度、郵便番号情報をどのように活用するかです。次の簡単なモバイルアプリケーション開発5 で、XML Web Service を使用して、W-Zero3だけではカバーできない情報へのアクセス方法について説明します。