GPS 신호를 수신하기 전에 GPS 신호에 대해 간략하게 한번 알아보자.
GPS 에서 제공하는 데이터는 NMEA(The National Marine Electronic Association) code 로 전송되는데, 원래 바다에서 배의 위치를 확인하기 위해 이용되던 녀석이 육지로도 올라와서 차량용 네비게이션등에 활용중이란다.
맞보기로 어떤 녀석들을 보내주는지 데이터를 한번 살펴 보자.
$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
일단 위 GPS 데이터는 Ascen GPS Logger(51CH.) 을 기준으로 나타냈으나, 시중의 다른 GPS 수신기들은 위 데이터외에 다른 데이터가 더 들어갈 수 있음을 미리 알아두자. 그럼 다른 데이터들도 모두 알아야 하느냐~?
그건 아니다. 결론을 먼저 이야기 하자면 위 신호중 우리는 $GPRMC 부분만을 사용할 것이다.
뭔가 잔뜩 들어가 있다.. @_@ 도대체 뭔 소린지는 아래에서 차근차근 살펴보자.
모든 신호는 한줄로 나타내고 줄바꿈으로 각각 구분된다.
첫 시작행은 $ 이고 그 이후 줄바꿈 문자가 나타나기 전까지 콤마(,)로 구분된 값들이 나타나는데 각 항목이 특별한 의미를 지니고 있다.
위 신호중 우리가 사용할 $GPRMC 의 각 부분의 의미하는 바는 아래와 같다.
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
앞에서부터 차례대로 적어보면 이렇다.
1. 시간 (ZuluTime)
2. 현재 수신 데이터의 유효 여부 : A (Valid), V (Invaild)
이 데이터가 A 일 경우만 의미가 있다. V 는 아직 위성이 고정되지 않았다는 말.
3. 위도 (Latitude)
4. North,South
5. 경도 (Longitude)
6. East,West
7. 속도(Knots) - 위에서 말한것 처럼 해상에서 배의 위치를 위해 나온것이라 단위가 knots 이다. 킬로미터로 환산하려면 약 1.8 정도를 곱하면 된다.
8. 진행방향(Track Angle) - 자북 방향 기준으로 현재 이동 방향을 나타낸다.
9. 날짜
10. checksum
그렇다. 여기서 속도와 진행방향만 parsing 하여 사용하면 된다.
GPS 에 대한 강좌가 아니므로 GPS 신호에 대한 이해는 대강 이정도로만 접어두고, 혹시 더 자세한 정보가 필요하다면 NMEA - http://www.gpsinformation.org/dale/nmea.htm 를 참고 하시라~! 이외의 수많은 sentence 들이 즐비하다.
그럼 이제 준비된 GPS 모듈로 부터 NMEA 데이터를 받아보자. 외부 GPS 수신기와 단말기(또는 테스트PC)가 블루투스로 연결되면 우리는 단순히 serial port 를 하나 열어 들어오는 NMEA 데이터를 족족~ 읽어들이기만 하면 된다.
Serial Port 를 열기 위해 System.IO.Ports.SerialPort 객체를 생성하고 아래와 같은 코드를 작성한다.
블루투스로 바인딩된 포트는 COM1 이라 가정하고 해당 Port 에 들어오는 NMEA 데이터를 가져오는 코드는 아래와 같다.
* Read Serial Port
this.serialPort.PortName = "COM1";
this.serialPort.Open();
string msg = this.serialPort.ReadExisting();
미리 생성된 Serial Port 객체에 바인딩할 COM1 이름만 할당하고 ReadExisting 으로 읽어들이면 되는데, 매초 단위로 읽어야 하므로 위 코드는 Timer 객체의 onTick Event Handler 로 등록해주면 된다.
이외에 해당 Port 의 내용을 가져올때 원활한 처리를 위한 delegate 몇개를 생성했다.
* Event Delegator
public delegate void on_FIND_PORT(object sender, int port);
public event on_FIND_PORT onFindPort;
public delegate void on_FOUND_PORT(object sender, int port);
public event on_FOUND_PORT onFoundPort;
public delegate void on_READ_PORT(object sender, string message);
public event on_READ_PORT onReadPort;
public delegate void on_PARSE_GPRMC(object sender, GPSInfo info);
public event on_PARSE_GPRMC onParseGPRMC;
이제 GPS 모듈로 부터 NMEA 데이터를 가져오기 위한 대강의 준비는 완료되었다.
그럼 이제 NMEA 데이터를 파싱하는 code 를 살펴보자
* NMEA Data Parser
public GPSInfo Parse(string msg)
{
if (msg == null || msg.Length <= 0)
{
throw new Exception("There are no data in port");
}
GPSInfo info = new GPSInfo();
info.Speed = 0;
info.Latitude = 0;
info.Longitude = 0;string[] lines = msg.Split('$');
IEnumerator ie = lines.GetEnumerator();
while (ie.MoveNext())
{
string[] items = ((string)ie.Current).Split(',');if (items[0].Equals("GPRMC"))
{
if (items[GPRMC_VALID].Equals("A"))
{
info.Speed = Double.Parse(items[GPRMC_KNOT]) * KNOT_TO_KMS;
info.Latitude = Double.Parse(items[GPRMC_LATITUDE]);
info.LatitudeType = items[GPRMC_LATITUDE_TYPE];
info.Longitude = Double.Parse(items[GPRMC_LONGITUDE]);
info.LongitudeType = items[GPRMC_LONGITUDE_TYPE];
info.Angle = Double.Parse(items[GPRMC_ANGLE]);
if (this.OnParseGPRMC != null)
{
onParseGPRMC(this, info);
}
}return info;
}
}return info;
}
정리하자면 라인단위로 split 하고 다시 , 단위로 split 한 뒤 각 요소위치별로 데이터를 추출하는 것인데, 이때 속도 부분만 1.83 을 곱해 knots 를 km/h 로 변환하였다.
가져온 값들은 각각 현재 이동 속도(Speed), 위도(Latitude), 경도(Longitude), 진행 방향(Angle)이다.
이제 이 값들을 적절히 화면에 출력하는 일만 남았다.