Na primeira parte sobre GPS (Entendendo o seu funcionamento) 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 NMEA
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:
Parte | Descrição |
---|---|
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.
|
|
Você pode conferir o código fonte completo aqui.
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.