map_pin

GPS Parte 2: Decodificando uma sentença NMEA

Na primeira parte sobre GPS vimos como este sistema de rastreamento por satélite funciona. Também aproveitamos para explicar o que é o protocolo NMEA, utilizado pelos sistemas de navegação global.

Uma sentença NMEA é formada por caracteres passíveis de impressão e CR (carriage return) e LF (line feed). Toda sentença inicia com $ e termina com <CR> <LF>. Existem três tipos básicos de sentenças: talker sentences, proprietary sentences e query sentences.

As talker sentences são as sentenças genéricas de comunicação do protocolo, já as proprietary sentences são sentenças proprietárias dos fabricantes e as query sentences são sentenças utilizadas para requisitar informações a partir de um receptor.

Neste artigo iremos ver como implementar um decodificar de sentenças do protocolo NMEA 0183 versão 2.3. Para implementar o decodificador iremos utilizar Python sem nenhuma biblioteca adicional. O código desenvolvido é apenas para ilustrar como é feito este tipo de processo, se você procura algo para utilizar em seu sistema eu recomendo a biblioteca pynmea2.

Decodificando uma sentença

Para decodificar uma sentença primeiro é necessário entender o seu funcionamento. No caso da sentença GGA (Global Positioning System Fix Data) podemos observar a descrição dos campos abaixo:

$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47

Para entender melhor, vamos explicar o que é cada parte:

GP Talker (GPS)
GGA Nome da sentença
123519 Hora da Fix (12:35:19 UTC)
4807.038,N Latitude 48 deg 07.038′ N
01131.000,E Longitude 11 deg 31.000′ E
1 Qualidade da Fix
08 Número de satélites visíveis
0.9 Posição horizontal
545.4,M Altitude, em metros, acima do nível do mar
46.9,M Nível médio do mar
(vazio) Tempo em segundos desde a última atualização do DGPS
(vazio) DGPS ID
*47 checksum*

Se você deseja conhecer as demais sentenças e seus campos eu recomento ler a especificação do protocolo NMEA 0183 (em inglês).

Bem, para entender melhor, vamos primeiro ver as principais partes do código, no fim será exibido o código por completo com alguns exemplos de uso.

Classe Sentence

A classe Sentence é a base de todas as sentenças NMEA. Toda sentença deve estender esta classe, como podemos observar na classe GGLSentence.

Toda sentença deve possuir um nome (sentence_name), uma descrição (sentence_description) e seus respectivos campos (fields). Além disto, as classes devem implementar um método validador (is_valid) para verificar se a sentença recebida é valida.

Podemos observar que existe um método para decodificar (parse) a sentença. Como as sentenças devem seguir o mesmo padrão, o método é genérico para todas as classes derivadas de Sentence.

Inicialmente é extraído da sentença o seu checksum caso exista. Após é verificado se a quantidade de campos recebidos na sentença corresponde a quantidade de campos registrados. Por fim, é convertido o valor do campo recebido para um tipo Python compatível.

Como sabemos para que tipo devemos converter determinado campo? É isso que você verá na classe GLLSentence.

Estendendo a classe Sentence

Na classe GLLSentence sobrescrevemos as propriedades necessárias para o funcionamento correto do parser.

Como podemos observar, sobrescrevemos os atributos sentence_name e sentence_description com o nome e a descrição da sentença.

O atributo fields foi sobrescrito por uma tupla de tuplas que corresponde ao seguinte: o primeiro valor da tupla é o nome do campo, deve sem um nome de atributo válido, pois ele será atribuído a classe em tempo de execução; o segundo campo é uma descrição para o campo, pode ser qualquer texto; o terceiro campo é uma função/classe que será utilizada para converter o valor. Neste caso deve-se lembrar que a função/classe deve possuir apenas um parâmetro e do tipo str. Isto porque a função/classe é invocada com o valor recebido no campo.

Se observarmos, é possível verificar que o campo latitude será convertido para str, já o campo ns_indicator será convertido para str, porém maiúscula. O campo utc_time usa a classe UTCTimeParser para converter para o tipo datetime.date.

Por fim, implementamos a validação da sentença. No caso da sentença GLL, ela é valida se o status for igual a A. Uma validação não implementada é a do checksum. O checksum serve para verificar se o conteúdo recebido foi o mesmo que o enviado. Como o intuito é apenas exemplificar o funcionamento, podemos ignorar este item.

Classe NMEAParser

Para finalizar, vamos verificar como a classe NMEAParser identifica qual a sentença e sua respectiva classe para conversão.

A classe NMEAParser possui um atributo (parsers) responsável por armazenar o nome da sentença e a classe de conversão. Para identificar qual a classe correta extraímos no método parse o nome da sentença recebida. Se não for possível identificar a sentença ou se recebermos uma sentença não suportada é gerada uma exceção.

Abaixo é possível observar o código completo da aplicação com alguns exemplos de uso.

Espero que você tenha gostado deste artigo. Na parte final desta série de artigos, iremos implementar uma pequena aplicação que captura a posição do GPS e informa em uma página Web.

Até a próxima.

Este artigo é uma adaptação da monografia BUSTRACKER: Sistema de rastreamento para transporte coletivo de Alexandre Vicenzi e o texto na íntegra pode ser encontrado aqui.