Repository: akolar/ogn-lib

Issue:
Add support for Fanet (OGNFNT)
See https://github.com/glidernet/ogn-aprs-protocol/pull/5/commits/1be92993c4d50db4329371d18385a29782ddc4fa for detailed message format.

Discussion:

Patch:
diff --git a/ogn_lib/parser.py b/ogn_lib/parser.py
index f3b51ae..5842f01 100644
--- a/ogn_lib/parser.py
+++ b/ogn_lib/parser.py
@@ -778,3 +778,44 @@ class Capturs(Parser):
     @staticmethod
     def _preprocess_message(message):
         return message.strip('/')
+
+
+class Fanet(Parser):
+    """
+    Parser for Fanet-formatted APRS messages.
+    """
+
+    __destto__ = ['OGNFNT', 'OGNFNT-1']
+
+    @staticmethod
+    def _parse_protocol_specific(comment):
+        """
+        Parses the comment string from Fanet's APRS messages.
+        :param str comment: comment string
+        :return: parsed comment
+        :rtype: dict
+        """
+
+        fields = comment.split(' ')
+
+        if len(fields) != 3:
+            raise exceptions.ParseError('Fanet comment incorrectly formatted:'
+                                        ' received {}'.format(comment))
+
+        lat_dig = int(fields[0][2])
+        lon_dig = int(fields[0][3])
+        update_position = [
+            {
+                'target': 'latitude',
+                'function': Parser._get_location_update_func(lat_dig)
+            }, {
+                'target': 'longitude',
+                'function': Parser._get_location_update_func(lon_dig)
+            }
+        ]
+
+        return {
+            '_update': update_position,
+            'id': fields[1],
+            'vertical_speed': Parser._convert_fpm_to_ms(fields[2])
+        }


Test patch:
diff --git a/tests/messages.txt b/tests/messages.txt
index 72b0c93..18ce385 100644
--- a/tests/messages.txt
+++ b/tests/messages.txt
@@ -80,3 +80,8 @@ FLRDDF944>OGSPID,qAS,SPIDER:/213930h3322.17S/07033.97W'332/028/A=003428 id300234
 ICA3E7540>OGSPOT,qAS,SPOT:/161427h1448.35S/04610.86W'000/000/A=008677 id0-2860357 SPOT3 GOOD
 ICA3E7540>OGSPOT,qAS,SPOT:/162923h1431.99S/04604.33W'000/000/A=006797 id0-2860357 SPOT3 GOOD
 ICA3E7540>OGSPOT,qAS,SPOT:/163421h1430.38S/04604.43W'000/000/A=007693 id0-2860357 SPOT3 GOOD
+FNT1103CE>OGNFNT,qAS,FNB1103CE:/183727h5057.94N/00801.00Eg355/002/A=001042 !W10! id1E1103CE +03fpm
+FNT1103CE>OGNFNT,qAS,FNB1103CE:/183729h5057.94N/00801.00Eg354/001/A=001042 !W10! id1E1103CE +07fpm
+FNT1103CE>OGNFNT,qAS,FNB1103CE:/183731h5057.94N/00801.00Eg354/001/A=001042 !W10! id1E1103CE +05fpm
+FNT1103CE>OGNFNT,qAS,FNB1103CE:/183734h5057.94N/00801.00Eg354/001/A=001042 !W30! id1E1103CE -10fpm
+FNT1103CE>OGNFNT,qAS,FNB1103CE:/183736h5057.94N/00801.00Eg354/001/A=001042 !W40! id1E1103CE -02fpm
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 937c193..ca88c62 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -580,6 +580,32 @@ class TestCapturs:
         parser.Capturs._preprocess_message.assert_called_once_with(msg)
 
 
+class TestFanet:
+
+    def test_parse_protocol_specific(self):
+        data = parser.Fanet._parse_protocol_specific('!W30! id1E1103CE 180fpm')
+
+        assert data['id'] == 'id1E1103CE'
+        assert abs(data['vertical_speed'] - 0.9144) < 0.01
+        assert (list(map(lambda x: x['target'], data['_update'])) ==
+                ['latitude', 'longitude'])
+
+    def test_parse_protocol_specific_fail(self):
+        with pytest.raises(exceptions.ParseError):
+            parser.Fanet._parse_protocol_specific('id1E1103CE')
+
+        with pytest.raises(exceptions.ParseError):
+            parser.Fanet._parse_protocol_specific(
+                '!W30! id1E1103CE -02fpm x')
+
+    def test_registered(self, mocker):
+        mocker.spy(parser.Fanet, '_parse_protocol_specific')
+        parser.Parser('FNT1103CE>OGNFNT,qAS,FNB1103CE:/183734h5057.94N/'
+                      '00801.00Eg354/001/A=001042 !W30! id1E1103CE -10fpm')
+        parser.Fanet._parse_protocol_specific.assert_called_once_with(
+            '!W30! id1E1103CE -10fpm')
+
+
 class TestServerParser:
 
     def test_parse_message_beacon(self, mocker):