Porównaj commity

...

121 Commity

Autor SHA1 Wiadomość Data
dependabot[bot] 471840f657
Bump coveralls from 3.2.0 to 3.3.1 (#105)
Bumps [coveralls](https://github.com/TheKevJames/coveralls-python) from 3.2.0 to 3.3.1.
- [Release notes](https://github.com/TheKevJames/coveralls-python/releases)
- [Changelog](https://github.com/TheKevJames/coveralls-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TheKevJames/coveralls-python/compare/3.2.0...3.3.1)

---
updated-dependencies:
- dependency-name: coveralls
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-12 00:54:28 +01:00
dependabot[bot] d5b8412c9c
Bump flake8 from 3.9.2 to 4.0.1 (#103)
Bumps [flake8](https://github.com/pycqa/flake8) from 3.9.2 to 4.0.1.
- [Release notes](https://github.com/pycqa/flake8/releases)
- [Commits](https://github.com/pycqa/flake8/compare/3.9.2...4.0.1)

---
updated-dependencies:
- dependency-name: flake8
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-12 00:46:09 +01:00
dependabot[bot] b0f3333fd0
Bump coveralls from 3.1.0 to 3.2.0 (#100) 2021-07-21 06:19:41 +00:00
Konstantin Gründger e58c1d8c0e Use timestamp from APRS server (fixes #85) 2021-06-13 00:04:13 +02:00
Konstantin Gründger 7eacc96313 Release v1.2.1 2021-06-06 12:39:36 +02:00
Konstantin Gründger 3e7ec2d917 Updated readme and changelog 2021-06-06 12:01:37 +02:00
Konstantin Gründger 0621307952 Added rainfall data to position_weather beacons 2021-06-06 12:00:52 +02:00
lemoidului 22d930b403
improve APRSClient log with peer ip addr (#99)
* improve APRSClient log with peer ip addr

* fix flake8 check
2021-06-02 23:24:10 +02:00
Konstantin Gründger 28848b6248 Release v1.2.0 2021-06-01 22:44:36 +02:00
Konstantin Gründger 7e1a7e75a9 Merge branch 'glidernet_master' 2021-06-01 22:30:18 +02:00
dependabot[bot] eddd3a5a72
Bump flake8 from 3.9.1 to 3.9.2 (#28)
Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.9.1 to 3.9.2.
- [Release notes](https://gitlab.com/pycqa/flake8/tags)
- [Commits](https://gitlab.com/pycqa/flake8/compare/3.9.1...3.9.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-01 15:46:42 +02:00
dependabot[bot] 0c53ccc5af
Bump coveralls from 3.0.1 to 3.1.0 (#29)
Bumps [coveralls](https://github.com/TheKevJames/coveralls-python) from 3.0.1 to 3.1.0.
- [Release notes](https://github.com/TheKevJames/coveralls-python/releases)
- [Changelog](https://github.com/TheKevJames/coveralls-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TheKevJames/coveralls-python/compare/3.0.1...3.1.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-01 15:42:30 +02:00
lemoidului 5291ddeaf4
add decode strict arg in run method (#98)
* add decode strict arg in run method

* adapt client.py unit tests with new run strict arg

* add ignore_decoding_error arg in APRSClient.run()
2021-05-31 20:45:16 +02:00
dependabot[bot] a5337c85b4
Bump flake8 from 3.9.1 to 3.9.2 (#96)
Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.9.1 to 3.9.2.
- [Release notes](https://gitlab.com/pycqa/flake8/tags)
- [Commits](https://gitlab.com/pycqa/flake8/compare/3.9.1...3.9.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-29 12:36:47 +02:00
dependabot[bot] 49f0519c5c
Bump coveralls from 3.0.1 to 3.1.0 (#97) 2021-05-28 16:59:28 +00:00
Konstantin Gründger 84d09c8b1b Merge branch 'master' of https://github.com/glidernet/python-ogn-client 2021-05-08 10:46:43 +02:00
Meisterschueler f83c4c71fb
Merge pull request #95 from lemoidului/master
add safesky parser
2021-05-08 10:43:35 +02:00
dependabot[bot] 3c36923b9a
Bump flake8 from 3.9.0 to 3.9.1 (#27)
Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.9.0 to 3.9.1.
- [Release notes](https://gitlab.com/pycqa/flake8/tags)
- [Commits](https://gitlab.com/pycqa/flake8/compare/3.9.0...3.9.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-08 10:37:00 +02:00
dependabot-preview[bot] ba79be2803
Upgrade to GitHub-native Dependabot (#26)
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2021-05-08 10:32:16 +02:00
lemoidului b51a3e95e6 add safesky stealth test in unit test 2021-05-01 18:11:21 +02:00
lemoidului 68f8b9b3a7 add safesky parser comment details 2021-05-01 17:51:31 +02:00
lemoidului faa73997d7 add safesky parser 2021-05-01 02:02:02 +02:00
Konstantin Gründger 464969a70e Release v1.1.0 2021-04-06 09:07:16 +02:00
dependabot-preview[bot] 8db0e7706b
Bump coveralls from 3.0.0 to 3.0.1 (#23)
Bumps [coveralls](https://github.com/TheKevJames/coveralls-python) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/TheKevJames/coveralls-python/releases)
- [Changelog](https://github.com/TheKevJames/coveralls-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TheKevJames/coveralls-python/compare/3.0.0...3.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2021-04-05 14:02:47 +02:00
dependabot-preview[bot] acc73561bc
Bump flake8 from 3.8.4 to 3.9.0 (#24)
Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.8.4 to 3.9.0.
- [Release notes](https://gitlab.com/pycqa/flake8/tags)
- [Commits](https://gitlab.com/pycqa/flake8/compare/3.8.4...3.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2021-04-05 13:59:05 +02:00
Konstantin Gründger edc470212c Correct wrong month 2021-04-05 13:49:16 +02:00
tfraudet 8fef35830e Fix aircraft_type decoding and add no-tracking flag decoding for Ogn & Flarm parsers 2021-04-05 13:32:27 +02:00
tfraudet f11e0efa97 Add test cases to support no-tracking flag 2021-04-05 13:24:13 +02:00
dependabot-preview[bot] 029b7add80
Bump coveralls from 2.1.2 to 3.0.0 (#22) 2021-01-17 12:07:55 +00:00
Konstantin Gründger bd0759eec0 Release v1.0.1 2020-11-02 21:46:59 +01:00
Konstantin Gründger 8b85415fef no logging messages by default (fixes #92) 2020-10-25 21:23:40 +01:00
Konstantin Gründger 74f6f9671f catch errors while connecting (fixes #74 and #91) 2020-10-25 21:21:58 +01:00
Konstantin Gründger a612ca9965 Release v1.0.0 2020-10-15 22:58:58 +02:00
Konstantin Gründger fe0257b4aa Added normalized_quality calculation 2020-10-14 09:01:09 +02:00
Konstantin Gründger ed1634603e Fixed test case 2020-10-14 09:00:17 +02:00
Konstantin Gründger f500b36fb6 aprs_type of position beacon with weather data is 'position_weather' 2020-10-14 09:00:11 +02:00
Konstantin Gründger e1c4623f64 put bearing into [0,360) 2020-10-12 20:28:09 +02:00
Konstantin Gründger 3d36b88996 Added fast bearing calculation 2020-10-12 19:59:41 +02:00
Konstantin Gründger 8dba8f7491 Fixed broken test on Travis CI 2020-10-11 14:52:27 +02:00
Konstantin Gründger e272ab0645 Changed the socket mode from blocking to timeout (fixes #89) 2020-10-11 14:01:55 +02:00
Konstantin Gründger 4a9a884d7c Added optionally distance calculation (fixes #86) 2020-10-10 21:53:39 +02:00
Konstantin Gründger 8855a4f097 Added support for weather data from FANET ground stations 2020-10-10 21:53:39 +02:00
Konstantin Gründger ffa6c8b1f0 Refactoring 2020-10-10 21:53:34 +02:00
dependabot-preview[bot] 9551d9c54e Bump flake8 from 3.8.3 to 3.8.4
Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.8.3 to 3.8.4.
- [Release notes](https://gitlab.com/pycqa/flake8/tags)
- [Commits](https://gitlab.com/pycqa/flake8/compare/3.8.3...3.8.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-05 10:01:43 +02:00
Konstantin Gründger 5a90299ca5 Refactoring 2020-10-04 11:06:28 +02:00
Konstantin Gründger 0c0ce12f25 Added support for latency in OGNSDR messages (fixes #87) 2020-10-04 11:02:27 +02:00
Konstantin Gründger 166023b352 Added tests for latency in OGNSDR messages 2020-10-04 10:58:24 +02:00
Konstantin Gründger bc3178f513 Added support for reference_timestamp with tzinfo (fixes #84) 2020-09-11 20:20:41 +02:00
Konstantin Gründger 7b60095366 Fixed capturs beacons (removed trailing forward slash) 2020-09-01 21:56:10 +02:00
Konstantin Gründger 0784ecdb17 Fix parser for correct textual altitude (fixes #81) 2020-09-01 21:56:00 +02:00
Konstantin Gründger a610900548 Skip all keys where value is "None" 2020-08-30 14:23:23 +02:00
Konstantin Gründger b69e733df4 Splitted keywords into array 2020-08-30 14:21:52 +02:00
Konstantin Gründger 92a8575378 All OGN fields are optional 2020-08-29 15:35:27 +02:00
Konstantin Gründger 1a9e1f2edd Removed old code 2020-08-29 15:05:05 +02:00
Konstantin Gründger 01fecbd1f0 Release v0.9.8 2020-08-21 18:04:36 +02:00
Meisterschueler f8f658a54c
Merge pull request #80 from Meisterschueler/master
Update dependencies
2020-08-12 21:55:46 +02:00
Meisterschueler ca4513a334
Merge pull request #79 from scls19fr/patch-1
Fix parse example
2020-08-12 13:44:46 +02:00
scls19fr 922616865b
Update README.md
Fix SyntaxError: invalid token
2020-08-12 12:22:08 +02:00
dependabot-preview[bot] 3862176244
Bump coveralls from 2.1.1 to 2.1.2 (#19) 2020-08-12 08:45:46 +00:00
Meisterschueler b8f6db0e2b
Merge pull request #76 from axelfahy/feat/kwargs-callback
Add kwargs for the callback function
2020-07-23 20:24:08 +02:00
Axel Fahy 62ee197add
Add entry for the kwargs of the callback function in the changelog 2020-07-23 09:41:44 +02:00
Axel Fahy 2c1612de93
Add kwargs for the callback function 2020-07-23 09:30:05 +02:00
Meisterschueler 7669ac4559
Merge pull request #77 from coisnepe/patch-1
Add syntax highlighting to README
2020-07-22 21:58:08 +02:00
Paul-Etienne Coisne 0a32f7519f
Add syntax highlighting to README 2020-07-22 13:23:58 +02:00
dependabot-preview[bot] 4d85b33429
Bump coveralls from 2.1.0 to 2.1.1 (#18) 2020-07-09 07:02:31 +00:00
dependabot-preview[bot] a1f6150cde
Bump coveralls from 2.0.0 to 2.1.0 (#17) 2020-07-07 07:25:44 +00:00
dependabot-preview[bot] 418c904885 Bump flake8 from 3.8.2 to 3.8.3
Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.8.2 to 3.8.3.
- [Release notes](https://gitlab.com/pycqa/flake8/tags)
- [Commits](https://gitlab.com/pycqa/flake8/compare/3.8.2...3.8.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-10 00:36:44 +02:00
dependabot-preview[bot] 1a60c7937b
Bump flake8 from 3.8.1 to 3.8.2 (#15) 2020-05-25 06:09:18 +00:00
Konstantin Gründger b495e6aec0 Changed InReach parser (fixed #73) 2020-05-24 14:54:51 +02:00
Konstantin Gründger 14fbcd7c12 Incompatible IDs from lt24, skylines, spider spot renamed to separate IDs, fixes #64 2020-05-22 10:43:41 +02:00
Konstantin Gründger 4968b9adf7 Refactoring 2020-05-21 23:14:31 +02:00
Konstantin Gründger 4d2557863f Release v0.9.7 2020-05-21 20:21:29 +02:00
dependabot-preview[bot] 1ec2603940 Bump coveralls from 1.10.0 to 2.0.0
Bumps [coveralls](https://github.com/coveralls-clients/coveralls-python) from 1.10.0 to 2.0.0.
- [Release notes](https://github.com/coveralls-clients/coveralls-python/releases)
- [Changelog](https://github.com/coveralls-clients/coveralls-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/coveralls-clients/coveralls-python/compare/1.10.0...2.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-21 20:09:05 +02:00
Konstantin Gründger 2d414f70e0 Added current python versions
updated development status
2020-05-21 20:04:06 +02:00
Konstantin Gründger 43f9ce4b7c Fixed deprecated function call 2020-05-21 19:31:15 +02:00
Konstantin Gründger e0f363be91 Fixed #72 2020-05-21 19:30:32 +02:00
Meisterschueler 858b0af032
Merge pull request #14 from Meisterschueler/dependabot/pip/flake8-3.8.1
Bump flake8 from 3.7.9 to 3.8.1
2020-05-13 18:54:36 +02:00
dependabot-preview[bot] 446f4766ea
Bump flake8 from 3.7.9 to 3.8.1
Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.7.9 to 3.8.1.
- [Release notes](https://gitlab.com/pycqa/flake8/tags)
- [Commits](https://gitlab.com/pycqa/flake8/compare/3.7.9...3.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-13 04:28:19 +00:00
Konstantin Gründger 3f36915c1b Release v0.9.6 2020-01-17 21:29:57 +01:00
Meisterschueler b1ce503c32
Merge pull request #69 from Meisterschueler/master
Merge dependabot changes
2020-01-15 19:00:05 +01:00
Meisterschueler 4d380d5eb7
Merge pull request #68 from zoranbosnjak/master
optional fields in flarm_parser
2020-01-15 18:52:45 +01:00
Zoran Bošnjak ab25b2b626 optional fields in flarm_parser
Make all fields optional. Fixes #67.
2020-01-15 10:42:42 +01:00
Meisterschueler f5f5a4f2e9
Merge pull request #9 from Meisterschueler/dependabot/pip/coveralls-1.10.0
Bump coveralls from 1.9.2 to 1.10.0
2020-01-07 12:30:11 +01:00
dependabot-preview[bot] 17c73b9ca5
Bump coveralls from 1.9.2 to 1.10.0
Bumps [coveralls](https://github.com/coveralls-clients/coveralls-python) from 1.9.2 to 1.10.0.
- [Release notes](https://github.com/coveralls-clients/coveralls-python/releases)
- [Changelog](https://github.com/coveralls-clients/coveralls-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/coveralls-clients/coveralls-python/compare/1.9.2...1.10.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-31 04:33:41 +00:00
Meisterschueler e06474398d
Merge pull request #66 from zoranbosnjak/master
Allow dynamic settings override
2019-12-20 22:43:20 +01:00
Zoran Bošnjak b48ee35e70 fix flake8 errors 2019-12-20 18:57:36 +01:00
Zoran Bošnjak 689d60ebbe Allow dynamic settings override
Additional (optional) 'settings' attribute in AprsClient,
to be able to pass custom settings to the client,
without changing the settings.py file.

Added 'CustomSettings' class for backward compatible attribute access.

Example (myapp.py):
...
args = ... parse arguments from command line

settings = CustomSettings(
    APRS_SERVER_HOST = args.server,
    APRS_SERVER_PORT_FULL_FEED = args.port,
    APRS_SERVER_PORT_CLIENT_DEFINED_FILTERS = args.port,
    APRS_APP_NAME = 'python-ogn-client',
    PACKAGE_VERSION = '0.9.5',
    APRS_APP_VER = '0.9',
    APRS_KEEPALIVE_TIME = 240,
    TELNET_SERVER_HOST = 'localhost',
    TELNET_SERVER_PORT = 50001,
    )

client = AprsClient(aprs_user='N0CALL', aprs_filter=args.filter, settings=settings)
client.connect()
...
2019-12-18 08:30:39 +01:00
dependabot-preview[bot] 58caf55f42 Bump coveralls from 1.8.2 to 1.9.2
Bumps [coveralls](https://github.com/coveralls-clients/coveralls-python) from 1.8.2 to 1.9.2.
- [Release notes](https://github.com/coveralls-clients/coveralls-python/releases)
- [Changelog](https://github.com/coveralls-clients/coveralls-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/coveralls-clients/coveralls-python/compare/1.8.2...1.9.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-07 16:06:56 +01:00
dependabot-preview[bot] 4b62b4002c Bump flake8 from 3.7.8 to 3.7.9
Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.7.8 to 3.7.9.
- [Release notes](https://gitlab.com/pycqa/flake8/tags)
- [Commits](https://gitlab.com/pycqa/flake8/compare/3.7.8...3.7.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-29 08:55:16 +01:00
Konstantin Gründger 226a8239fa Release v0.9.5 2019-09-07 16:26:32 +02:00
dependabot-preview[bot] 8446140bce Bump coveralls from 1.8.1 to 1.8.2
Bumps [coveralls](https://github.com/coveralls-clients/coveralls-python) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/coveralls-clients/coveralls-python/releases)
- [Changelog](https://github.com/coveralls-clients/coveralls-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/coveralls-clients/coveralls-python/compare/1.8.1...1.8.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-30 08:24:15 +02:00
Konstantin Gründger 7260245e15 Changed telnet parser from fixed size to regex 2019-07-20 11:11:19 +02:00
dependabot-preview[bot] 1892cce7b3 Bump flake8 from 3.7.7 to 3.7.8
Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.7.7 to 3.7.8.
- [Release notes](https://gitlab.com/pycqa/flake8/tags)
- [Commits](https://gitlab.com/pycqa/flake8/compare/3.7.7...3.7.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-09 08:37:34 +02:00
Konstantin Gründger 2e02015e52 Fixed telnet parser (see #18) 2019-07-07 12:48:57 +02:00
dependabot-preview[bot] f85455cc51 Bump coveralls from 1.8.0 to 1.8.1
Bumps [coveralls](https://github.com/coveralls-clients/coveralls-python) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/coveralls-clients/coveralls-python/releases)
- [Changelog](https://github.com/coveralls-clients/coveralls-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/coveralls-clients/coveralls-python/compare/1.8.0...1.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-18 08:23:28 +02:00
Konstantin Gründger 2d2a003e8a Release v0.9.4 2019-06-10 18:05:31 +02:00
Konstantin Gründger 342af0a253 Add support for capturs (fixes #42) 2019-06-08 14:48:42 +02:00
Konstantin Gründger 37d089acac Bugfix Spider parser (fixes #60) 2019-06-08 14:20:19 +02:00
Konstantin Gründger b7f51b92f8 Added support for OGFLYM beacons (fixes #63) 2019-06-08 14:02:18 +02:00
Konstantin Gründger 706a725305 Allow comment in tracker status beacon (fixes #56) 2019-06-08 08:03:34 +02:00
Konstantin Gründger 7682a69fd7 Fixed inReach parser 2019-06-07 20:50:54 +02:00
Konstantin Gründger b8f0028f1e Small changes for inReach parser 2019-06-07 20:34:50 +02:00
Konstantin Gründger 1eae059cf6 Fix some flake8 problems 2019-06-07 20:08:51 +02:00
Konstantin Gründger 061d47bb03 Reduce flask8 warnings 2019-06-07 20:08:51 +02:00
dependabot-preview[bot] 002d92a42b Bump flake8 from 3.4.1 to 3.7.7
Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.4.1 to 3.7.7.
- [Release notes](https://gitlab.com/pycqa/flake8/tags)
- [Commits](https://gitlab.com/pycqa/flake8/compare/3.4.1...3.7.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-07 20:07:10 +02:00
dependabot-preview[bot] e9d461b8ce Bump coveralls from 1.2 to 1.8.0
Bumps [coveralls](https://github.com/coveralls-clients/coveralls-python) from 1.2 to 1.8.0.
- [Release notes](https://github.com/coveralls-clients/coveralls-python/releases)
- [Changelog](https://github.com/coveralls-clients/coveralls-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/coveralls-clients/coveralls-python/compare/1.2.0...1.8.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-07 20:07:10 +02:00
Meisterschueler a07d07be9a
Merge pull request #65 from rocketman768/feature/inreach-parse
Add Inreach parser
2019-06-07 20:05:59 +02:00
Philip Lee d76b7525e6 Add a blank line in the test case for CI? 2019-06-05 20:17:05 -07:00
Philip Lee 55e3696a34 Add Inreach parser
Based on documentation here...
https://github.com/glidernet/ogn-aprs-protocol/blob/master/aprsmsgs.txt
2019-06-05 20:11:59 -07:00
Konstantin Gründger 37de8673c2 Release v0.9.3 2019-06-03 23:19:53 +02:00
Konstantin Gründger 7a577ab9ef Added generic parser for unknown protocol 2019-06-03 22:45:31 +02:00
Konstantin Gründger dab3ca3ddb Use precompiled regex 2019-06-03 22:45:31 +02:00
Konstantin Gründger b0bf5c82ee Release v0.9.2 2019-05-07 23:27:21 +02:00
Meisterschueler 4780a08500
Merge pull request #62 from glidernet/clementallen_master
Bugfix for #61
2019-05-07 22:45:49 +02:00
Matt b6659bb216 Bugfix for #61 2019-05-07 21:20:54 +02:00
Konstantin Gründger ae58ad7f0d Fixes #54 2018-09-29 23:19:01 +02:00
Konstantin Gründger 0ec3dc277d Release v0.9.1 2018-09-18 19:12:05 +02:00
Konstantin Gründger 4a2c2429f1 Update changelog 2018-09-18 19:01:39 +02:00
Konstantin Gründger 32064f5364 Fix SPOT and Tracker parser 2018-09-18 19:01:34 +02:00
Konstantin Gründger 595519053d Change conversion factor kph->ms (fixes #53) 2018-09-18 18:37:01 +02:00
Konstantin Gründger 84dbfdecdb Catch ConnectionResetError (fixes #52) 2018-09-18 18:35:44 +02:00
57 zmienionych plików z 1067 dodań i 394 usunięć

8
.github/dependabot.yml vendored 100644
Wyświetl plik

@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10

Wyświetl plik

@ -1,9 +1,11 @@
language: python
python:
- 3.4
- 3.5
- 3.6
- 3.7
- 3.8
- 3.9-dev
before_script:
- flake8 tests ogn

Wyświetl plik

@ -1,4 +1,66 @@
# CHANGELOG
## unreleased
- client: If no reference_timestamp provided use timestamp from APRS server (fixes #85)
## 1.2.1: - 2021-06-06
- client: Added peer IP to log messages
- parser: Added rainfall_1h and rainfall_24h to beacon_type 'position_weather'
## 1.2.0: - 2021-06-01
- parser: Added support for OGNSKY (safesky) beacons
- client: Replace bad characters with <20> instead of raising an exception (restore old behaviour with parameter ignore_decoding_error=False)
## 1.1.0: - 2021-04-05
- parser: Added no-tracking flag decoding
- parser: Fixed aircraft_type decoding
## 1.0.1: - 2020-11-02
- client: catch errors while connecting (fixes #74 and #91)
- client: no logging messages by default (fixes #92)
## 1.0.0: - 2020-10-15
- client: changed socket mode from blocking to 5s timeout (fixes #89)
- parser: Added optional distance/bearing/normalized_quality calculation if parameter "calculate_relatives" is True (fixes #86)
- parser: Added support for weather data (new in receiver software v0.2.8) from FANET ground stations (aprs_type: position_weather)
- parser: Added support for latency (new in receiver software v0.2.8) in receiver messages (OGNSDR) (fixes #87)
- parser: Added support for reference_timestamp with tzinfo (fixes #84)
- parser: Fixed textual altitude part (fixes #81)
- parser: Skip keys where value is "None"
## 0.9.8: - 2020-08-21
- parser: Changed InReach parser (fixes #73)
- parser: separated incompatible ID into parser dependant ID (lt24: address -> lt24_id, skylines: address -> skylines_id,
spider: id_spider -> spider_registration, address -> spider_id, spot: address -> spot_id) (fixes #64)
- client: Added keyword arguments for the callback function in the 'run' method of the client
## 0.9.7: - 2020-05-21
- parser: Added support for OGPAW (PilotAware) beacons
- client: Dropped compatibility for Python 3.4
## 0.9.6: - 2020-01-17
- parser: Better support for OGFLR beacons from PilotAware
- client: Allow dynamic settings override with optional "settings" parameter
## 0.9.5: - 2019-09-07
- parser: fixed telnet parser
## 0.9.4: - 2019-06-10
- parser: Added support for OGINREACH (Garmin inReach) beacons
- parser: Added support for OGFLYM (Flymaster) beacons
- parser: Added support for comments in tracker beacons (OGNTRK)
- parser: Added support for OGCAPT (Capturs) beacons
## 0.9.3: - 2019-06-03
- parser: Added Generic parser for unknown formats
## 0.9.2: - 2019-05-07
- parser: Exception handling for bad OGNTRK beacons
## 0.9.1: - 2018-09-18
- parser: Fixed SPOT beacons and Tracker beacons
- parser: Fixed kph to ms conversion
- client: Catch ConnectionResetError
## 0.9.0: - 2018-05-14
- parser: Added support for OGNLT24 (LT24), OGSKYL (Skylines), OGSPID (Spider), OGSPOT (Spot) and OGNFNT (Fanet)
- parser: Added support for (server) comments

Wyświetl plik

@ -20,28 +20,30 @@ pip install ogn-client
## Example Usage
Parse APRS/OGN packet.
### Parse APRS/OGN packet.
```
```python
from ogn.parser import parse
from datetime import datetime
beacon = parse("FLRDDDEAD>APRS,qAS,EDER:/114500h5029.86N/00956.98E'342/049/A=005524 id0ADDDEAD -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5",
reference_timestamp=datetime(2015, 07, 31, 12, 34, 56))
reference_timestamp=datetime(2015, 7, 31, 12, 34, 56))
```
Connect to OGN and display all incoming beacons.
### Connect to OGN and display all incoming beacons.
```
```python
from ogn.client import AprsClient
from ogn.parser import parse, ParseError
def process_beacon(raw_message):
try:
beacon = parse(raw_message)
print('Received {beacon_type}: {raw_message}'.format(**beacon))
print('Received {aprs_type}: {raw_message}'.format(**beacon))
except ParseError as e:
print('Error, {}'.format(e.message))
except NotImplementedError as e:
print('{}: {}'.format(e, raw_message))
client = AprsClient(aprs_user='N0CALL')
client.connect()
@ -53,5 +55,26 @@ except KeyboardInterrupt:
client.disconnect()
```
### Connect to telnet console and display all decoded beacons.
```python
from ogn.client import TelnetClient
from ogn.parser.telnet_parser import parse
def process_beacon(raw_message):
beacon = parse(raw_message)
if beacon:
print(beacon)
client = TelnetClient()
client.connect()
try:
client.run(callback=process_beacon)
except KeyboardInterrupt:
print('\nStop ogn gateway')
client.disconnect()
```
## License
Licensed under the [AGPLv3](LICENSE).

Wyświetl plik

@ -1 +1,10 @@
from ogn.client.client import AprsClient # flake8: noqa
from ogn.client.client import AprsClient # noqa: F401
from ogn.client.client import TelnetClient # noqa: F401
class CustomSettings(object):
def __init__(self, **kw):
self.kw = kw
def __getattr__(self, name):
return self.kw[name]

Wyświetl plik

@ -13,86 +13,110 @@ def create_aprs_login(user_name, pass_code, app_name, app_version, aprs_filter=N
class AprsClient:
def __init__(self, aprs_user, aprs_filter=''):
def __init__(self, aprs_user, aprs_filter='', settings=settings):
self.logger = logging.getLogger(__name__)
self.logger.info("Connect to OGN as {} with filter '{}'".format(aprs_user, (aprs_filter if aprs_filter else 'full-feed')))
self.logger.addHandler(logging.NullHandler())
self.aprs_user = aprs_user
self.aprs_filter = aprs_filter
self.settings = settings
self._sock_peer_ip = None
self._kill = False
def connect(self):
def connect(self, retries=1, wait_period=15):
# create socket, connect to server, login and make a file object associated with the socket
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
while retries > 0:
retries -= 1
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.sock.settimeout(5)
if self.aprs_filter:
port = settings.APRS_SERVER_PORT_CLIENT_DEFINED_FILTERS
else:
port = settings.APRS_SERVER_PORT_FULL_FEED
if self.aprs_filter:
port = self.settings.APRS_SERVER_PORT_CLIENT_DEFINED_FILTERS
else:
port = self.settings.APRS_SERVER_PORT_FULL_FEED
self.sock.connect((settings.APRS_SERVER_HOST, port))
self.logger.debug('Server port {}'.format(port))
self.sock.connect((self.settings.APRS_SERVER_HOST, port))
self._sock_peer_ip = self.sock.getpeername()[0]
login = create_aprs_login(self.aprs_user, -1, settings.APRS_APP_NAME, settings.APRS_APP_VER, self.aprs_filter)
self.sock.send(login.encode())
self.sock_file = self.sock.makefile('rw')
login = create_aprs_login(self.aprs_user, -1, self.settings.APRS_APP_NAME, self.settings.APRS_APP_VER, self.aprs_filter)
self.sock.send(login.encode())
self.sock_file = self.sock.makefile('rb')
self._kill = False
self._kill = False
self.logger.info("Connect to OGN ({}/{}:{}) as {} with filter: {}".
format(self.settings.APRS_SERVER_HOST, self._sock_peer_ip, port, self.aprs_user,
"'" + self.aprs_filter + "'" if self.aprs_filter else 'none (full-feed)'))
break
except (socket.error, ConnectionError) as e:
self.logger.error('Connect error: {}'.format(e))
if retries > 0:
self.logger.info('Waiting {}s before next connection try ({} attempts left).'.format(wait_period, retries))
sleep(wait_period)
else:
self._kill = True
self.logger.critical('Could not connect to OGN.')
def disconnect(self):
self.logger.info('Disconnect')
self.logger.info('Disconnect from {}'.format(self._sock_peer_ip))
try:
# close everything
self.sock.shutdown(0)
self.sock.close()
except OSError:
self.logger.error('Socket close error', exc_info=True)
self.logger.error('Socket close error')
self._kill = True
def run(self, callback, timed_callback=lambda client: None, autoreconnect=False):
def run(self, callback, timed_callback=lambda client: None, autoreconnect=False, ignore_decoding_error=True,
**kwargs):
while not self._kill:
try:
keepalive_time = time()
while not self._kill:
if time() - keepalive_time > settings.APRS_KEEPALIVE_TIME:
self.logger.info('Send keepalive')
if time() - keepalive_time > self.settings.APRS_KEEPALIVE_TIME:
self.logger.info('Send keepalive to {}'.format(self._sock_peer_ip))
self.sock.send('#keepalive\n'.encode())
timed_callback(self)
keepalive_time = time()
# Read packet string from socket
packet_str = self.sock_file.readline().strip()
packet_b = self.sock_file.readline().strip()
packet_str = packet_b.decode(errors="replace") if ignore_decoding_error else packet_b.decode()
# A zero length line should not be return if keepalives are being sent
# A zero length line will only be returned after ~30m if keepalives are not sent
if len(packet_str) == 0:
self.logger.warning('Read returns zero length string. Failure. Orderly closeout')
self.logger.warning('Read returns zero length string. Failure. Orderly closeout from {}'.
format(self._sock_peer_ip))
break
callback(packet_str)
except BrokenPipeError:
self.logger.error('BrokenPipeError', exc_info=True)
callback(packet_str, **kwargs)
except socket.error:
self.logger.error('socket.error', exc_info=True)
self.logger.error('socket.error')
except OSError:
self.logger.error('OSError')
except UnicodeDecodeError:
self.logger.error('UnicodeDecodeError', exc_info=True)
self.logger.error('UnicodeDecodeError')
self.logger.debug(packet_b)
if autoreconnect and not self._kill:
self.connect()
self.connect(retries=100)
else:
return
class TelnetClient:
def __init__(self):
def __init__(self, settings=settings):
self.logger = logging.getLogger(__name__)
self.logger.info("Connect to local telnet server")
self.settings = settings
def connect(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((settings.TELNET_SERVER_HOST, settings.TELNET_SERVER_PORT))
self.sock.connect((self.settings.TELNET_SERVER_HOST, self.settings.TELNET_SERVER_PORT))
def run(self, callback, autoreconnect=False):
while True:

Wyświetl plik

@ -4,7 +4,7 @@ APRS_SERVER_PORT_CLIENT_DEFINED_FILTERS = 14580
APRS_APP_NAME = 'python-ogn-client'
PACKAGE_VERSION = '0.9.0'
PACKAGE_VERSION = '1.2.1'
APRS_APP_VER = PACKAGE_VERSION[:3]
APRS_KEEPALIVE_TIME = 240

Wyświetl plik

@ -1 +1 @@
from ogn.ddb.utils import get_ddb_devices # flake8: noqa
from ogn.ddb.utils import get_ddb_devices # noqa: F401

Wyświetl plik

@ -1,3 +1,3 @@
from ogn.parser import parse as parse_module # only for test functions. Without this a mock of parse would mock the function instead of the module
from ogn.parser.parse import parse, parse_aprs, parse_comment # flake8: noqa
from ogn.parser.exceptions import ParseError, AprsParseError, OgnParseError # flake8: noqa
from ogn.parser import parse as parse_module # noqa: F40 --- only for test functions. Without this a mock of parse would mock the function instead of the module
from ogn.parser.parse import parse, parse_aprs, parse_comment # noqa: F401
from ogn.parser.exceptions import ParseError, AprsParseError, OgnParseError # noqa: F401

Wyświetl plik

@ -3,9 +3,9 @@ class BaseParser():
self.beacon_type = 'unknown'
def parse(self, aprs_comment, aprs_type):
if aprs_type == "position":
if aprs_type.startswith('position'):
data = self.parse_position(aprs_comment)
elif aprs_type == "status":
elif aprs_type.startswith('status'):
data = self.parse_status(aprs_comment)
else:
raise ValueError("aprs_type {} unknown".format(aprs_type))

Wyświetl plik

@ -1,7 +1,5 @@
import re
from ogn.parser.utils import FPM_TO_MS
from ogn.parser.pattern import PATTERN_FANET_POSITION_COMMENT
from ogn.parser.pattern import PATTERN_FANET_POSITION_COMMENT, PATTERN_FANET_STATUS_COMMENT
from .base import BaseParser
@ -9,12 +7,28 @@ from .base import BaseParser
class FanetParser(BaseParser):
def __init__(self):
self.beacon_type = 'fanet'
self.position_pattern = PATTERN_FANET_POSITION_COMMENT
self.status_pattern = PATTERN_FANET_STATUS_COMMENT
@staticmethod
def parse_position(aprs_comment):
ac_match = re.search(PATTERN_FANET_POSITION_COMMENT, aprs_comment)
return {'address_type': int(ac_match.group('details'), 16) & 0b00000011 if ac_match.group('details') else None,
'aircraft_type': (int(ac_match.group('details'), 16) & 0b01111100) >> 2 if ac_match.group('details') else None,
'stealth': (int(ac_match.group('details'), 16) & 0b10000000) >> 7 == 1 if ac_match.group('details') else None,
'address': ac_match.group('address') if ac_match.group('address') else None,
'climb_rate': int(ac_match.group('climb_rate')) * FPM_TO_MS if ac_match.group('climb_rate') else None}
def parse_position(self, aprs_comment):
match = self.position_pattern.match(aprs_comment)
result = {}
if match.group('details'):
result.update({
'address_type': int(match.group('details'), 16) & 0b00000011,
'aircraft_type': (int(match.group('details'), 16) & 0b01111100) >> 2,
'stealth': (int(match.group('details'), 16) & 0b10000000) >> 7 == 1,
'address': match.group('address')
})
if match.group('climb_rate'): result['climb_rate'] = int(match.group('climb_rate')) * FPM_TO_MS
return result
def parse_status(self, aprs_comment):
match = self.status_pattern.match(aprs_comment)
result = {}
if match.group('fanet_name'): result['fanet_name'] = match.group('fanet_name')
if match.group('signal_quality'): result['signal_quality'] = float(match.group('signal_quality'))
if match.group('frequency_offset'): result['frequency_offset'] = float(match.group('frequency_offset'))
if match.group('error_count'): result['error_count'] = int(match.group('error_count'))
return result

Wyświetl plik

@ -1,5 +1,3 @@
import re
from ogn.parser.pattern import PATTERN_FLARM_POSITION_COMMENT
from ogn.parser.utils import FPM_TO_MS, HPM_TO_DEGS
@ -9,22 +7,34 @@ from .base import BaseParser
class FlarmParser(BaseParser):
def __init__(self):
self.beacon_type = 'flarm'
self.position_pattern = PATTERN_FLARM_POSITION_COMMENT
@staticmethod
def parse_position(aprs_comment):
ac_match = re.search(PATTERN_FLARM_POSITION_COMMENT, aprs_comment)
return {'address_type': int(ac_match.group('details'), 16) & 0b00000011,
'aircraft_type': (int(ac_match.group('details'), 16) & 0b01111100) >> 2,
'stealth': (int(ac_match.group('details'), 16) & 0b10000000) >> 7 == 1,
'address': ac_match.group('address'),
'climb_rate': int(ac_match.group('climb_rate')) * FPM_TO_MS,
'turn_rate': float(ac_match.group('turn_rate')) * HPM_TO_DEGS,
'signal_quality': float(ac_match.group('signal_quality')),
'error_count': int(ac_match.group('error_count')),
'frequency_offset': float(ac_match.group('frequency_offset')),
'gps_quality': {'horizontal': int(ac_match.group('gps_quality_horizontal')),
'vertical': int(ac_match.group('gps_quality_vertical'))} if ac_match.group('gps_quality') else None,
'software_version': float(ac_match.group('software_version')) if ac_match.group('software_version') else None,
'hardware_version': int(ac_match.group('hardware_version'), 16) if ac_match.group('hardware_version') else None,
'real_address': ac_match.group('real_address') if ac_match.group('real_address') else None,
'signal_power': float(ac_match.group('signal_power')) if ac_match.group('signal_power') else None}
def parse_position(self, aprs_comment):
match = self.position_pattern.match(aprs_comment)
result = {}
if match.group('details'):
result.update({
'address_type': int(match.group('details'), 16) & 0b00000011,
'aircraft_type': (int(match.group('details'), 16) & 0b00111100) >> 2,
'no-tracking': (int(match.group('details'), 16) & 0b01000000) >> 6 == 1,
'stealth': (int(match.group('details'), 16) & 0b10000000) >> 7 == 1,
'address': match.group('address'),
})
if match.group('climb_rate'): result['climb_rate'] = int(match.group('climb_rate')) * FPM_TO_MS
if match.group('turn_rate'): result['turn_rate'] = float(match.group('turn_rate')) * HPM_TO_DEGS
if match.group('signal_quality'): result['signal_quality'] = float(match.group('signal_quality'))
if match.group('error_count'): result['error_count'] = int(match.group('error_count'))
if match.group('frequency_offset'): result['frequency_offset'] = float(match.group('frequency_offset'))
if match.group('gps_quality'):
result.update({
'gps_quality': {
'horizontal': int(match.group('gps_quality_horizontal')),
'vertical': int(match.group('gps_quality_vertical'))
}
})
if match.group('software_version'): result['software_version'] = float(match.group('software_version'))
if match.group('hardware_version'): result['hardware_version'] = int(match.group('hardware_version'), 16)
if match.group('real_address'): result['real_address'] = match.group('real_address')
if match.group('signal_power'): result['signal_power'] = float(match.group('signal_power'))
return result

Wyświetl plik

@ -0,0 +1,12 @@
from .base import BaseParser
class GenericParser(BaseParser):
def __init__(self, beacon_type='unknown'):
self.beacon_type = beacon_type
def parse_position(self, aprs_comment):
return {'comment': aprs_comment}
def parse_status(self, aprs_comment):
return {'comment': aprs_comment}

Wyświetl plik

@ -0,0 +1,16 @@
from ogn.parser.pattern import PATTERN_INREACH_POSITION_COMMENT
from .base import BaseParser
class InreachParser(BaseParser):
def __init__(self):
self.beacon_type = 'inreach'
self.position_pattern = PATTERN_INREACH_POSITION_COMMENT
def parse_position(self, aprs_comment):
match = self.position_pattern.match(aprs_comment)
return {'address': match.group('id'),
'model': match.group('model') if match.group('model') else None,
'status': match.group('status') == 'True' if match.group('status') else None,
'pilot_name': match.group('pilot_name') if match.group('pilot_name') else None}

Wyświetl plik

@ -1,5 +1,3 @@
import re
from ogn.parser.utils import FPM_TO_MS
from ogn.parser.pattern import PATTERN_LT24_POSITION_COMMENT
@ -9,10 +7,10 @@ from .base import BaseParser
class LT24Parser(BaseParser):
def __init__(self):
self.beacon_type = 'lt24'
self.position_pattern = PATTERN_LT24_POSITION_COMMENT
@staticmethod
def parse_position(aprs_comment):
ac_match = re.search(PATTERN_LT24_POSITION_COMMENT, aprs_comment)
return {'address': ac_match.group('id'),
'climb_rate': int(ac_match.group('climb_rate')) * FPM_TO_MS if ac_match.group('climb_rate') else None,
'source': ac_match.group('source') if ac_match.group('source') else None}
def parse_position(self, aprs_comment):
match = self.position_pattern.match(aprs_comment)
return {'lt24_id': match.group('lt24_id'),
'climb_rate': int(match.group('climb_rate')) * FPM_TO_MS if match.group('climb_rate') else None,
'source': match.group('source') if match.group('source') else None}

Wyświetl plik

@ -1,5 +1,3 @@
import re
from ogn.parser.pattern import PATTERN_NAVITER_POSITION_COMMENT
from ogn.parser.utils import FPM_TO_MS, HPM_TO_DEGS
@ -9,10 +7,10 @@ from .base import BaseParser
class NaviterParser(BaseParser):
def __init__(self):
self.beacon_type = 'naviter'
self.position_pattern = PATTERN_NAVITER_POSITION_COMMENT
@staticmethod
def parse_position(aprs_comment):
match = re.search(PATTERN_NAVITER_POSITION_COMMENT, aprs_comment)
def parse_position(self, aprs_comment):
match = self.position_pattern.match(aprs_comment)
return {'stealth': (int(match.group('details'), 16) & 0b1000000000000000) >> 15 == 1,
'do_not_track': (int(match.group('details'), 16) & 0b0100000000000000) >> 14 == 1,
'aircraft_type': (int(match.group('details'), 16) & 0b0011110000000000) >> 10,

Wyświetl plik

@ -1,5 +1,3 @@
import re
from ogn.parser.utils import FPM_TO_MS, HPM_TO_DEGS
from ogn.parser.pattern import PATTERN_RECEIVER_BEACON, PATTERN_AIRCRAFT_BEACON
@ -9,71 +7,86 @@ from .base import BaseParser
class OgnParser(BaseParser):
def __init__(self):
self.beacon_type = None
self.aircraft_pattern = PATTERN_AIRCRAFT_BEACON
self.receiver_pattern = PATTERN_RECEIVER_BEACON
def parse(self, aprs_comment, aprs_type):
if not aprs_comment:
return {'beacon_type': 'aprs_receiver'}
ac_data = self.parse_aircraft_beacon(aprs_comment)
if ac_data:
ac_data.update({'beacon_type': 'aprs_aircraft'})
return ac_data
ab_data = self.parse_aircraft_beacon(aprs_comment)
if ab_data:
ab_data.update({'beacon_type': 'aprs_aircraft'})
return ab_data
rc_data = self.parse_receiver_beacon(aprs_comment)
if rc_data:
rc_data.update({'beacon_type': 'aprs_receiver'})
return rc_data
rb_data = self.parse_receiver_beacon(aprs_comment)
if rb_data:
rb_data.update({'beacon_type': 'aprs_receiver'})
return rb_data
else:
return {'user_comment': aprs_comment,
'beacon_type': 'aprs_receiver'}
@staticmethod
def parse_aircraft_beacon(aprs_comment):
ac_match = re.search(PATTERN_AIRCRAFT_BEACON, aprs_comment)
if ac_match:
return {'address_type': int(ac_match.group('details'), 16) & 0b00000011,
'aircraft_type': (int(ac_match.group('details'), 16) & 0b01111100) >> 2,
'stealth': (int(ac_match.group('details'), 16) & 0b10000000) >> 7 == 1,
'address': ac_match.group('address'),
'climb_rate': int(ac_match.group('climb_rate')) * FPM_TO_MS if ac_match.group('climb_rate') else None,
'turn_rate': float(ac_match.group('turn_rate')) * HPM_TO_DEGS if ac_match.group('turn_rate') else None,
'flightlevel': float(ac_match.group('flight_level')) if ac_match.group('flight_level') else None,
'signal_quality': float(ac_match.group('signal_quality')) if ac_match.group('signal_quality') else None,
'error_count': int(ac_match.group('errors')) if ac_match.group('errors') else None,
'frequency_offset': float(ac_match.group('frequency_offset')) if ac_match.group('frequency_offset') else None,
'gps_quality': {'horizontal': int(ac_match.group('gps_quality_horizontal')),
'vertical': int(ac_match.group('gps_quality_vertical'))} if ac_match.group('gps_quality') else None,
'software_version': float(ac_match.group('flarm_software_version')) if ac_match.group('flarm_software_version') else None,
'hardware_version': int(ac_match.group('flarm_hardware_version'), 16) if ac_match.group('flarm_hardware_version') else None,
'real_address': ac_match.group('flarm_id') if ac_match.group('flarm_id') else None,
'signal_power': float(ac_match.group('signal_power')) if ac_match.group('signal_power') else None,
'proximity': [hear[4:] for hear in ac_match.group('proximity').split(" ")] if ac_match.group('proximity') else None}
def parse_aircraft_beacon(self, aprs_comment):
match = self.aircraft_pattern.match(aprs_comment)
if match:
result = {}
if match.group('details'):
result.update({
'address_type': int(match.group('details'), 16) & 0b00000011,
'aircraft_type': (int(match.group('details'), 16) & 0b00111100) >> 2,
'no-tracking': (int(match.group('details'), 16) & 0b01000000) >> 6 == 1,
'stealth': (int(match.group('details'), 16) & 0b10000000) >> 7 == 1,
'address': match.group('address'),
})
if match.group('climb_rate'): result['climb_rate'] = int(match.group('climb_rate')) * FPM_TO_MS
if match.group('turn_rate'): result['turn_rate'] = float(match.group('turn_rate')) * HPM_TO_DEGS
if match.group('flight_level'): result['flightlevel'] = float(match.group('flight_level'))
if match.group('signal_quality'): result['signal_quality'] = float(match.group('signal_quality'))
if match.group('errors'): result['error_count'] = int(match.group('errors'))
if match.group('frequency_offset'): result['frequency_offset'] = float(match.group('frequency_offset'))
if match.group('gps_quality'):
result.update({
'gps_quality': {
'horizontal': int(match.group('gps_quality_horizontal')),
'vertical': int(match.group('gps_quality_vertical'))
}
})
if match.group('flarm_software_version'): result['software_version'] = float(match.group('flarm_software_version'))
if match.group('flarm_hardware_version'): result['hardware_version'] = int(match.group('flarm_hardware_version'), 16)
if match.group('flarm_id'): result['real_address'] = match.group('flarm_id')
if match.group('signal_power'): result['signal_power'] = float(match.group('signal_power'))
if match.group('proximity'): result['proximity'] = [hear[4:] for hear in match.group('proximity').split(' ')]
return result
else:
return None
@staticmethod
def parse_receiver_beacon(aprs_comment):
rec_match = re.search(PATTERN_RECEIVER_BEACON, aprs_comment)
if rec_match:
return {'version': rec_match.group('version'),
'platform': rec_match.group('platform'),
'cpu_load': float(rec_match.group('cpu_load')),
'free_ram': float(rec_match.group('ram_free')),
'total_ram': float(rec_match.group('ram_total')),
'ntp_error': float(rec_match.group('ntp_offset')),
'rt_crystal_correction': float(rec_match.group('ntp_correction')),
'voltage': float(rec_match.group('voltage')) if rec_match.group('voltage') else None,
'amperage': float(rec_match.group('amperage')) if rec_match.group('amperage') else None,
'cpu_temp': float(rec_match.group('cpu_temperature')) if rec_match.group('cpu_temperature') else None,
'senders_visible': int(rec_match.group('visible_senders')) if rec_match.group('visible_senders') else None,
'senders_total': int(rec_match.group('senders')) if rec_match.group('senders') else None,
'rec_crystal_correction': int(rec_match.group('rf_correction_manual')) if rec_match.group('rf_correction_manual') else None,
'rec_crystal_correction_fine': float(rec_match.group('rf_correction_automatic')) if rec_match.group('rf_correction_automatic') else None,
'rec_input_noise': float(rec_match.group('signal_quality')) if rec_match.group('signal_quality') else None,
'senders_signal': float(rec_match.group('senders_signal_quality')) if rec_match.group('senders_signal_quality') else None,
'senders_messages': float(rec_match.group('senders_messages')) if rec_match.group('senders_messages') else None,
'good_senders_signal': float(rec_match.group('good_senders_signal_quality')) if rec_match.group('good_senders_signal_quality') else None,
'good_senders': float(rec_match.group('good_senders')) if rec_match.group('good_senders') else None,
'good_and_bad_senders': float(rec_match.group('good_and_bad_senders')) if rec_match.group('good_and_bad_senders') else None}
def parse_receiver_beacon(self, aprs_comment):
match = self.receiver_pattern.match(aprs_comment)
if match:
result = {
'version': match.group('version'),
'platform': match.group('platform'),
'cpu_load': float(match.group('cpu_load')),
'free_ram': float(match.group('ram_free')),
'total_ram': float(match.group('ram_total')),
'ntp_error': float(match.group('ntp_offset')),
'rt_crystal_correction': float(match.group('ntp_correction'))
}
if match.group('voltage'): result['voltage'] = float(match.group('voltage'))
if match.group('amperage'): result['amperage'] = float(match.group('amperage'))
if match.group('cpu_temperature'): result['cpu_temp'] = float(match.group('cpu_temperature'))
if match.group('visible_senders'): result['senders_visible'] = int(match.group('visible_senders'))
if match.group('senders'): result['senders_total'] = int(match.group('senders'))
if match.group('latency'): result['latency'] = float(match.group('latency'))
if match.group('rf_correction_manual'): result['rec_crystal_correction'] = int(match.group('rf_correction_manual'))
if match.group('rf_correction_automatic'): result['rec_crystal_correction_fine'] = float(match.group('rf_correction_automatic'))
if match.group('signal_quality'): result['rec_input_noise'] = float(match.group('signal_quality'))
if match.group('senders_signal_quality'): result['senders_signal'] = float(match.group('senders_signal_quality'))
if match.group('senders_messages'): result['senders_messages'] = float(match.group('senders_messages'))
if match.group('good_senders_signal_quality'): result['good_senders_signal'] = float(match.group('good_senders_signal_quality'))
if match.group('good_senders'): result['good_senders'] = float(match.group('good_senders'))
if match.group('good_and_bad_senders'): result['good_and_bad_senders'] = float(match.group('good_and_bad_senders'))
return result
else:
return None

Wyświetl plik

@ -1,5 +1,3 @@
import re
from ogn.parser.pattern import PATTERN_RECEIVER_POSITION_COMMENT, PATTERN_RECEIVER_STATUS_COMMENT
from .base import BaseParser
@ -8,35 +6,40 @@ from .base import BaseParser
class ReceiverParser(BaseParser):
def __init__(self):
self.beacon_type = 'receiver'
self.position_pattern = PATTERN_RECEIVER_POSITION_COMMENT
self.status_pattern = PATTERN_RECEIVER_STATUS_COMMENT
@staticmethod
def parse_position(aprs_comment):
def parse_position(self, aprs_comment):
if aprs_comment is None:
return {}
else:
match = re.search(PATTERN_RECEIVER_POSITION_COMMENT, aprs_comment)
match = self.position_pattern.match(aprs_comment)
return {'user_comment': match.group('user_comment') if match.group('user_comment') else None}
@staticmethod
def parse_status(aprs_comment):
match = re.search(PATTERN_RECEIVER_STATUS_COMMENT, aprs_comment)
return {'version': match.group('version'),
'platform': match.group('platform'),
'cpu_load': float(match.group('cpu_load')),
'free_ram': float(match.group('ram_free')),
'total_ram': float(match.group('ram_total')),
'ntp_error': float(match.group('ntp_offset')),
'rt_crystal_correction': float(match.group('ntp_correction')),
'voltage': float(match.group('voltage')) if match.group('voltage') else None,
'amperage': float(match.group('amperage')) if match.group('amperage') else None,
'cpu_temp': float(match.group('cpu_temperature')) if match.group('cpu_temperature') else None,
'senders_visible': int(match.group('visible_senders')) if match.group('visible_senders') else None,
'senders_total': int(match.group('senders')) if match.group('senders') else None,
'rec_crystal_correction': int(match.group('rf_correction_manual')) if match.group('rf_correction_manual') else None,
'rec_crystal_correction_fine': float(match.group('rf_correction_automatic')) if match.group('rf_correction_automatic') else None,
'rec_input_noise': float(match.group('signal_quality')) if match.group('signal_quality') else None,
'senders_signal': float(match.group('senders_signal_quality')) if match.group('senders_signal_quality') else None,
'senders_messages': float(match.group('senders_messages')) if match.group('senders_messages') else None,
'good_senders_signal': float(match.group('good_senders_signal_quality')) if match.group('good_senders_signal_quality') else None,
'good_senders': float(match.group('good_senders')) if match.group('good_senders') else None,
'good_and_bad_senders': float(match.group('good_and_bad_senders')) if match.group('good_and_bad_senders') else None}
def parse_status(self, aprs_comment):
match = self.status_pattern.match(aprs_comment)
result = {
'version': match.group('version'),
'platform': match.group('platform'),
'cpu_load': float(match.group('cpu_load')),
'free_ram': float(match.group('ram_free')),
'total_ram': float(match.group('ram_total')),
'ntp_error': float(match.group('ntp_offset')),
}
if match.group('ntp_correction'): result['rt_crystal_correction'] = float(match.group('ntp_correction'))
if match.group('voltage'): result['voltage'] = float(match.group('voltage'))
if match.group('amperage'): result['amperage'] = float(match.group('amperage'))
if match.group('cpu_temperature'): result['cpu_temp'] = float(match.group('cpu_temperature'))
if match.group('visible_senders'): result['senders_visible'] = int(match.group('visible_senders'))
if match.group('senders'): result['senders_total'] = int(match.group('senders'))
if match.group('rf_correction_manual'): result['rec_crystal_correction'] = int(match.group('rf_correction_manual'))
if match.group('rf_correction_automatic'): result['rec_crystal_correction_fine'] = float(match.group('rf_correction_automatic'))
if match.group('signal_quality'): result['rec_input_noise'] = float(match.group('signal_quality'))
if match.group('senders_signal_quality'): result['senders_signal'] = float(match.group('senders_signal_quality'))
if match.group('senders_messages'): result['senders_messages'] = float(match.group('senders_messages'))
if match.group('good_senders_signal_quality'): result['good_senders_signal'] = float(match.group('good_senders_signal_quality'))
if match.group('good_senders'): result['good_senders'] = float(match.group('good_senders'))
if match.group('good_and_bad_senders'): result['good_and_bad_senders'] = float(match.group('good_and_bad_senders'))
return result

Wyświetl plik

@ -0,0 +1,32 @@
from ogn.parser.utils import FPM_TO_MS
from ogn.parser.pattern import PATTERN_SAFESKY_POSITION_COMMENT
from .base import BaseParser
class SafeskyParser(BaseParser):
def __init__(self):
self.beacon_type = 'safesky'
self.position_pattern = PATTERN_SAFESKY_POSITION_COMMENT
def parse_position(self, aprs_comment):
match = self.position_pattern.match(aprs_comment)
result = dict()
if match.group('details'):
result.update({
'address_type': int(match.group('details'), 16) & 0b00000011,
'aircraft_type': (int(match.group('details'), 16) & 0b00111100) >> 2,
'no-tracking': (int(match.group('details'), 16) & 0b01000000) >> 6 == 1,
'stealth': (int(match.group('details'), 16) & 0b10000000) >> 7 == 1,
'address': match.group('address'),
})
result.update(
{'climb_rate': int(match.group('climb_rate')) * FPM_TO_MS if match.group('climb_rate') else None})
if match.group('gps_quality'):
result.update({
'gps_quality': {
'horizontal': int(match.group('gps_quality_horizontal')),
'vertical': int(match.group('gps_quality_vertical'))
}
})
return result

Wyświetl plik

@ -1,5 +1,3 @@
import re
from ogn.parser.utils import FPM_TO_MS
from ogn.parser.pattern import PATTERN_SKYLINES_POSITION_COMMENT
@ -9,9 +7,9 @@ from .base import BaseParser
class SkylinesParser(BaseParser):
def __init__(self):
self.beacon_type = 'skylines'
self.position_pattern = PATTERN_SKYLINES_POSITION_COMMENT
@staticmethod
def parse_position(aprs_comment):
ac_match = re.search(PATTERN_SKYLINES_POSITION_COMMENT, aprs_comment)
return {'address': ac_match.group('id'),
'climb_rate': int(ac_match.group('climb_rate')) * FPM_TO_MS if ac_match.group('climb_rate') else None}
def parse_position(self, aprs_comment):
match = self.position_pattern.match(aprs_comment)
return {'skylines_id': match.group('skylines_id'),
'climb_rate': int(match.group('climb_rate')) * FPM_TO_MS if match.group('climb_rate') else None}

Wyświetl plik

@ -1,5 +1,3 @@
import re
from ogn.parser.pattern import PATTERN_SPIDER_POSITION_COMMENT
from .base import BaseParser
@ -8,11 +6,11 @@ from .base import BaseParser
class SpiderParser(BaseParser):
def __init__(self):
self.beacon_type = 'spider'
self.position_pattern = PATTERN_SPIDER_POSITION_COMMENT
@staticmethod
def parse_position(aprs_comment):
ac_match = re.search(PATTERN_SPIDER_POSITION_COMMENT, aprs_comment)
return {'address': ac_match.group('id'),
'signal_power': int(ac_match.group('signal_power')) if ac_match.group('signal_power') else None,
'spider_id': ac_match.group('spider_id') if ac_match.group('spider_id') else None,
'gps_quality': ac_match.group('gps_quality') if ac_match.group('gps_quality') else None}
def parse_position(self, aprs_comment):
match = self.position_pattern.match(aprs_comment)
return {'spider_id': match.group('spider_id'),
'signal_power': int(match.group('signal_power')) if match.group('signal_power') else None,
'spider_registration': match.group('spider_registration') if match.group('spider_registration') else None,
'gps_quality': match.group('gps_quality') if match.group('gps_quality') else None}

Wyświetl plik

@ -1,5 +1,3 @@
import re
from ogn.parser.pattern import PATTERN_SPOT_POSITION_COMMENT
from .base import BaseParser
@ -8,10 +6,10 @@ from .base import BaseParser
class SpotParser(BaseParser):
def __init__(self):
self.beacon_type = 'spot'
self.position_pattern = PATTERN_SPOT_POSITION_COMMENT
@staticmethod
def parse_position(aprs_comment):
ac_match = re.search(PATTERN_SPOT_POSITION_COMMENT, aprs_comment)
return {'address': ac_match.group('id'),
'model': int(ac_match.group('model')) if ac_match.group('model') else None,
'status': ac_match.group('status') if ac_match.group('status') else None}
def parse_position(self, aprs_comment):
match = self.position_pattern.match(aprs_comment)
return {'spot_id': match.group('spot_id'),
'model': match.group('model') if match.group('model') else None,
'status': match.group('status') if match.group('status') else None}

Wyświetl plik

@ -1,5 +1,3 @@
import re
from ogn.parser.pattern import PATTERN_TRACKER_POSITION_COMMENT, PATTERN_TRACKER_STATUS_COMMENT
from ogn.parser.utils import FPM_TO_MS, HPM_TO_DEGS
@ -9,36 +7,53 @@ from .base import BaseParser
class TrackerParser(BaseParser):
def __init__(self):
self.beacon_type = 'tracker'
self.position_pattern = PATTERN_TRACKER_POSITION_COMMENT
self.status_pattern = PATTERN_TRACKER_STATUS_COMMENT
@staticmethod
def parse_position(aprs_comment):
match = re.search(PATTERN_TRACKER_POSITION_COMMENT, aprs_comment)
return {'address_type': int(match.group('details'), 16) & 0b00000011,
def parse_position(self, aprs_comment):
match = self.position_pattern.match(aprs_comment)
result = {}
if match.group('details'):
result.update({
'address_type': int(match.group('details'), 16) & 0b00000011,
'aircraft_type': (int(match.group('details'), 16) & 0b01111100) >> 2,
'stealth': (int(match.group('details'), 16) & 0b10000000) >> 7 == 1,
'address': match.group('address'),
'climb_rate': int(match.group('climb_rate')) * FPM_TO_MS if match.group('climb_rate') else None,
'turn_rate': float(match.group('turn_rate')) * HPM_TO_DEGS if match.group('turn_rate') else None,
'flightlevel': float(match.group('flight_level')) if match.group('flight_level') else None,
'signal_quality': float(match.group('signal_quality')) if match.group('signal_quality') else None,
'error_count': int(match.group('error_count')) if match.group('error_count') else None,
'frequency_offset': float(match.group('frequency_offset')) if match.group('frequency_offset') else None,
'gps_quality': {'horizontal': int(match.group('gps_quality_horizontal')),
'vertical': int(match.group('gps_quality_vertical'))} if match.group('gps_quality') else None,
'signal_power': float(match.group('signal_power')) if match.group('signal_power') else None}
})
if match.group('climb_rate'): result['climb_rate'] = int(match.group('climb_rate')) * FPM_TO_MS
if match.group('turn_rate'): result['turn_rate'] = float(match.group('turn_rate')) * HPM_TO_DEGS
if match.group('flight_level'): result['flightlevel'] = float(match.group('flight_level'))
if match.group('signal_quality'): result['signal_quality'] = float(match.group('signal_quality'))
if match.group('error_count'): result['error_count'] = int(match.group('error_count'))
if match.group('frequency_offset'): result['frequency_offset'] = float(match.group('frequency_offset'))
if match.group('gps_quality'):
result.update({
'gps_quality': {
'horizontal': int(match.group('gps_quality_horizontal')),
'vertical': int(match.group('gps_quality_vertical'))
}
})
if match.group('signal_power'): result['signal_power'] = float(match.group('signal_power'))
return result
@staticmethod
def parse_status(aprs_comment):
match = re.search(PATTERN_TRACKER_STATUS_COMMENT, aprs_comment)
return {'hardware_version': int(match.group('hardware_version')) if match.group('hardware_version') else None,
'software_version': int(match.group('software_version')) if match.group('software_version') else None,
'gps_satellites': int(match.group('gps_satellites')) if match.group('gps_satellites') else None,
'gps_quality': int(match.group('gps_quality')) if match.group('gps_quality') else None,
'gps_altitude': int(match.group('gps_altitude')) if match.group('gps_altitude') else None,
'pressure': float(match.group('pressure')) if match.group('pressure') else None,
'temperature': float(match.group('temperature')) if match.group('temperature') else None,
'humidity': int(match.group('humidity')) if match.group('humidity') else None,
'voltage': float(match.group('voltage')) if match.group('voltage') else None,
'transmitter_power': int(match.group('transmitter_power')) if match.group('transmitter_power') else None,
'noise_level': float(match.group('noise_level')) if match.group('noise_level') else None,
'relays': int(match.group('relays')) if match.group('relays') else None}
def parse_status(self, aprs_comment):
match = self.status_pattern.match(aprs_comment)
if match:
result = {}
if match.group('hardware_version'): result['hardware_version'] = int(match.group('hardware_version'))
if match.group('software_version'): result['software_version'] = int(match.group('software_version'))
if match.group('gps_satellites'): result['gps_satellites'] = int(match.group('gps_satellites'))
if match.group('gps_quality'): result['gps_quality'] = int(match.group('gps_quality'))
if match.group('gps_altitude'): result['gps_altitude'] = int(match.group('gps_altitude'))
if match.group('pressure'): result['pressure'] = float(match.group('pressure'))
if match.group('temperature'): result['temperature'] = float(match.group('temperature'))
if match.group('humidity'): result['humidity'] = int(match.group('humidity'))
if match.group('voltage'): result['voltage'] = float(match.group('voltage'))
if match.group('transmitter_power'): result['transmitter_power'] = int(match.group('transmitter_power'))
if match.group('noise_level'): result['noise_level'] = float(match.group('noise_level'))
if match.group('relays'): result['relays'] = int(match.group('relays'))
return result
else:
return {'comment': aprs_comment}

Wyświetl plik

@ -1,9 +1,9 @@
import re
from datetime import datetime
from ogn.parser.utils import createTimestamp, parseAngle, KNOTS_TO_MS, KPH_TO_MS, FEETS_TO_METER
from ogn.parser.pattern import PATTERN_APRS, PATTERN_APRS_POSITION, PATTERN_APRS_STATUS, PATTERN_SERVER
from ogn.parser.exceptions import AprsParseError, OgnParseError
from ogn.parser.utils import createTimestamp, parseAngle, KNOTS_TO_MS, KPH_TO_MS, FEETS_TO_METER, INCH_TO_MM, fahrenheit_to_celsius, CheapRuler, normalized_quality
from ogn.parser.pattern import PATTERN_APRS, PATTERN_APRS_POSITION, PATTERN_APRS_POSITION_WEATHER, PATTERN_APRS_STATUS, PATTERN_SERVER
from ogn.parser.exceptions import AprsParseError
from ogn.parser.aprs_comment.ogn_parser import OgnParser
from ogn.parser.aprs_comment.fanet_parser import FanetParser
@ -15,17 +15,40 @@ from ogn.parser.aprs_comment.receiver_parser import ReceiverParser
from ogn.parser.aprs_comment.skylines_parser import SkylinesParser
from ogn.parser.aprs_comment.spider_parser import SpiderParser
from ogn.parser.aprs_comment.spot_parser import SpotParser
from ogn.parser.aprs_comment.inreach_parser import InreachParser
from ogn.parser.aprs_comment.safesky_parser import SafeskyParser
from ogn.parser.aprs_comment.generic_parser import GenericParser
positions = {}
server_timestamp = None
def parse(aprs_message, reference_timestamp=None):
if reference_timestamp is None:
def parse(aprs_message, reference_timestamp=None, calculate_relations=False, use_server_timestamp=True):
global positions
global server_timestamp
if use_server_timestamp is True:
reference_timestamp = server_timestamp or datetime.utcnow()
elif reference_timestamp is None:
reference_timestamp = datetime.utcnow()
message = parse_aprs(aprs_message, reference_timestamp)
message = parse_aprs(aprs_message, reference_timestamp=reference_timestamp)
if message['aprs_type'] == 'position' or message['aprs_type'] == 'status':
message.update(parse_comment(message['comment'],
dstcall=message['dstcall'],
aprs_type=message['aprs_type']))
if message['aprs_type'].startswith('position') and calculate_relations is True:
positions[message['name']] = (message['longitude'], message['latitude'])
if message['receiver_name'] in positions:
cheap_ruler = CheapRuler((message['latitude'] + positions[message['receiver_name']][1]) / 2.0)
message['distance'] = cheap_ruler.distance((message['longitude'], message['latitude']), positions[message['receiver_name']])
message['bearing'] = cheap_ruler.bearing((message['longitude'], message['latitude']), positions[message['receiver_name']])
message['normalized_quality'] = normalized_quality(message['distance'], message['signal_quality']) if 'signal_quality' in message else None
if message['aprs_type'] == 'server':
server_timestamp = message['timestamp']
return message
@ -50,11 +73,10 @@ def parse_aprs(message, reference_timestamp=None):
result.update({
'comment': message,
'aprs_type': 'comment'})
else:
match = re.search(PATTERN_APRS, message)
if match:
aprs_type = 'position' if match.group('aprs_type') == '/' else 'status'
aprs_type = 'position' if match.group('aprs_type') == '/' else 'status' if match.group('aprs_type') == '>' else 'unknown'
result.update({'aprs_type': aprs_type})
aprs_body = match.group('aprs_body')
if aprs_type == 'position':
@ -66,18 +88,52 @@ def parse_aprs(message, reference_timestamp=None):
'relay': match.group('relay') if match.group('relay') else None,
'receiver_name': match.group('receiver'),
'timestamp': createTimestamp(match_position.group('time'), reference_timestamp),
'latitude': parseAngle('0' + match_position.group('latitude') + (match_position.group('latitude_enhancement') or '0')) *
'latitude': parseAngle('0' + match_position.group('latitude') + (match_position.group('latitude_enhancement') or '0')) * # noqa: W504
(-1 if match_position.group('latitude_sign') == 'S' else 1),
'symboltable': match_position.group('symbol_table'),
'longitude': parseAngle(match_position.group('longitude') + (match_position.group('longitude_enhancement') or '0')) *
'longitude': parseAngle(match_position.group('longitude') + (match_position.group('longitude_enhancement') or '0')) * # noqa: W504
(-1 if match_position.group('longitude_sign') == 'W' else 1),
'symbolcode': match_position.group('symbol'),
'track': int(match_position.group('course')) if match_position.group('course_extension') else None,
'ground_speed': int(match_position.group('ground_speed')) * KNOTS_TO_MS / KPH_TO_MS if match_position.group('ground_speed') else None,
'altitude': int(match_position.group('altitude')) * FEETS_TO_METER,
'comment': match_position.group('comment') if match_position.group('comment') else ""})
else:
raise AprsParseError(message)
'altitude': int(match_position.group('altitude')) * FEETS_TO_METER if match_position.group('altitude') else None,
'comment': match_position.group('comment') if match_position.group('comment') else "",
})
return result
match_position_weather = re.search(PATTERN_APRS_POSITION_WEATHER, aprs_body)
if match_position_weather:
result.update({
'aprs_type': 'position_weather',
'name': match.group('callsign'),
'dstcall': match.group('dstcall'),
'relay': match.group('relay') if match.group('relay') else None,
'receiver_name': match.group('receiver'),
'timestamp': createTimestamp(match_position_weather.group('time'), reference_timestamp),
'latitude': parseAngle('0' + match_position_weather.group('latitude')) * # noqa: W504
(-1 if match_position_weather.group('latitude_sign') == 'S' else 1),
'symboltable': match_position_weather.group('symbol_table'),
'longitude': parseAngle(match_position_weather.group('longitude')) * # noqa: W504
(-1 if match_position_weather.group('longitude_sign') == 'W' else 1),
'symbolcode': match_position_weather.group('symbol'),
'wind_direction': int(match_position_weather.group('wind_direction')) if match_position_weather.group('wind_direction') != '...' else None,
'wind_speed': int(match_position_weather.group('wind_speed')) * KNOTS_TO_MS / KPH_TO_MS if match_position_weather.group('wind_speed') != '...' else None,
'wind_speed_peak': int(match_position_weather.group('wind_speed_peak')) * KNOTS_TO_MS / KPH_TO_MS if match_position_weather.group('wind_speed_peak') != '...' else None,
'temperature': fahrenheit_to_celsius(float(match_position_weather.group('temperature'))) if match_position_weather.group('temperature') != '...' else None,
'rainfall_1h': int(match_position_weather.group('rainfall_1h')) / 100.0 * INCH_TO_MM if match_position_weather.group('rainfall_1h') else None,
'rainfall_24h': int(match_position_weather.group('rainfall_24h')) / 100.0 * INCH_TO_MM if match_position_weather.group('rainfall_24h') else None,
'humidity': int(match_position_weather.group('humidity')) * 0.01 if match_position_weather.group('humidity') else None,
'barometric_pressure': int(match_position_weather.group('barometric_pressure')) if match_position_weather.group('barometric_pressure') else None,
'comment': match_position_weather.group('comment') if match_position_weather.group('comment') else "",
})
return result
raise AprsParseError(message)
elif aprs_type == 'status':
match_status = re.search(PATTERN_APRS_STATUS, aprs_body)
if match_status:
@ -88,7 +144,7 @@ def parse_aprs(message, reference_timestamp=None):
'timestamp': createTimestamp(match_status.group('time'), reference_timestamp),
'comment': match_status.group('comment') if match_status.group('comment') else ""})
else:
raise AprsParseError(message)
raise NotImplementedError(message)
else:
raise AprsParseError(message)
@ -100,11 +156,17 @@ dstcall_parser_mapping = {'APRS': OgnParser(),
'OGFLR': FlarmParser(),
'OGNTRK': TrackerParser(),
'OGNSDR': ReceiverParser(),
'OGCAPT': GenericParser(beacon_type='capturs'),
'OGFLYM': GenericParser(beacon_type='flymaster'),
'OGINRE': InreachParser(),
'OGLT24': LT24Parser(),
'OGNAVI': NaviterParser(),
'OGPAW': GenericParser(beacon_type='pilot_aware'),
'OGSKYL': SkylinesParser(),
'OGSPID': SpiderParser(),
'OGSPOT': SpotParser(),
'OGNSKY': SafeskyParser(),
'GENERIC': GenericParser(beacon_type='unknown'),
}
@ -113,4 +175,4 @@ def parse_comment(aprs_comment, dstcall='APRS', aprs_type="position"):
if parser:
return parser.parse(aprs_comment, aprs_type)
else:
raise OgnParseError("No parser for dstcall {} found. APRS comment: {}".format(dstcall, aprs_comment))
return dstcall_parser_mapping.get('GENERIC').parse(aprs_comment, aprs_type)

Wyświetl plik

@ -1,16 +1,24 @@
import re
PATTERN_APRS = re.compile(r"^(?P<callsign>.+?)>(?P<dstcall>[A-Z0-9]+),((?P<relay>[A-Za-z0-9]+)\*)?.*,(?P<receiver>.+?):(?P<aprs_type>(/|>))(?P<aprs_body>.*)$")
PATTERN_APRS_POSITION = re.compile(r"^(?P<time>(([0-1]\d|2[0-3])[0-5]\d[0-5]\dh|([0-2]\d|3[0-1])([0-1]\d|2[0-3])[0-5]\dz))(?P<latitude>9000\.00|[0-8]\d{3}\.\d{2})(?P<latitude_sign>N|S)(?P<symbol_table>.)(?P<longitude>18000\.00|1[0-7]\d{3}\.\d{2}|0\d{4}\.\d{2})(?P<longitude_sign>E|W)(?P<symbol>.)(?P<course_extension>(?P<course>\d{3})/(?P<ground_speed>\d{3}))?/A=(?P<altitude>(-\d{5}|\d{6}))(?P<pos_extension>\s!W((?P<latitude_enhancement>\d)(?P<longitude_enhancement>\d))!)?(?:\s(?P<comment>.*))?$")
PATTERN_APRS = re.compile(r"^(?P<callsign>.+?)>(?P<dstcall>[A-Z0-9]+),((?P<relay>[A-Za-z0-9]+)\*)?.*,(?P<receiver>.+?):(?P<aprs_type>(.))(?P<aprs_body>.*)$")
PATTERN_APRS_POSITION = re.compile(r"^(?P<time>(([0-1]\d|2[0-3])[0-5]\d[0-5]\dh|([0-2]\d|3[0-1])([0-1]\d|2[0-3])[0-5]\dz))(?P<latitude>9000\.00|[0-8]\d{3}\.\d{2})(?P<latitude_sign>N|S)(?P<symbol_table>.)(?P<longitude>18000\.00|1[0-7]\d{3}\.\d{2}|0\d{4}\.\d{2})(?P<longitude_sign>E|W)(?P<symbol>.)(?P<course_extension>(?P<course>\d{3})/(?P<ground_speed>\d{3}))?(/A=(?P<altitude>(-\d{5}|\d{6})))?(?P<pos_extension>\s!W((?P<latitude_enhancement>\d)(?P<longitude_enhancement>\d))!)?(?:\s(?P<comment>.*))?$")
PATTERN_APRS_POSITION_WEATHER = re.compile(r"^(?P<time>(([0-1]\d|2[0-3])[0-5]\d[0-5]\dh|([0-2]\d|3[0-1])([0-1]\d|2[0-3])[0-5]\dz))(?P<latitude>9000\.00|[0-8]\d{3}\.\d{2})(?P<latitude_sign>N|S)(?P<symbol_table>.)(?P<longitude>18000\.00|1[0-7]\d{3}\.\d{2}|0\d{4}\.\d{2})(?P<longitude_sign>E|W)(?P<symbol>.)(?P<wind_direction>(\d{3}|\.{3}))/(?P<wind_speed>(\d{3}|\.{3}))g(?P<wind_speed_peak>(\d{3}|\.{3}))t(?P<temperature>(\d{3}|\.{3}))(r(?P<rainfall_1h>\d{3}))?(p(?P<rainfall_24h>\d{3}))?(h(?P<humidity>\d{2}))?(b(?P<barometric_pressure>\d{5}))?(?:\s(?P<comment>.*))?$")
PATTERN_APRS_STATUS = re.compile(r"^(?P<time>(([0-1]\d|2[0-3])[0-5]\d[0-5]\dh|([0-2]\d|3[0-1])([0-1]\d|2[0-3])[0-5]\dz))\s(?P<comment>.*)$")
PATTERN_SERVER = re.compile(r"^# aprsc (?P<version>[a-z0-9\.\-]+) (?P<timestamp>\d+ [A-Za-z]+ \d+ \d{2}:\d{2}:\d{2} GMT) (?P<server>[A-Z0-9]+) (?P<ip_address>\d+\.\d+\.\d+\.\d+):(?P<port>\d+)$")
PATTERN_FANET_POSITION_COMMENT = re.compile("""
PATTERN_FANET_POSITION_COMMENT = re.compile(r"""
(id(?P<details>[\dA-F]{2})(?P<address>[\dA-F]{6}?)\s?)?
(?:(?P<climb_rate>[+-]\d+)fpm)?
""", re.VERBOSE | re.MULTILINE)
PATTERN_FANET_STATUS_COMMENT = re.compile(r"""
(?:(Name=\"(?P<fanet_name>[^\"]*)\")\s?)?
(?:(?P<signal_quality>[\d.]+?)dB\s?)?
(?:(?P<frequency_offset>[+-][\d.]+?)kHz\s?)?
(?:(?P<error_count>\d+)e\s?)?
""", re.VERBOSE | re.MULTILINE)
PATTERN_FLARM_POSITION_COMMENT = re.compile(r"""
id(?P<details>[\dA-F]{2})(?P<address>[\dA-F]{6}?)\s?
(?:(?P<climb_rate>[+-]\d+?)fpm\s)?
@ -25,37 +33,44 @@ PATTERN_FLARM_POSITION_COMMENT = re.compile(r"""
(?:(?P<signal_power>[+-][\d.]+)dBm\s?)?
""", re.VERBOSE | re.MULTILINE)
PATTERN_LT24_POSITION_COMMENT = re.compile("""
id(?P<id>\d+)\s
PATTERN_LT24_POSITION_COMMENT = re.compile(r"""
id(?P<lt24_id>\d+)\s
(?P<climb_rate>[+-]\d+)fpm\s
(?P<source>.+)
""", re.VERBOSE | re.MULTILINE)
PATTERN_NAVITER_POSITION_COMMENT = re.compile("""
PATTERN_NAVITER_POSITION_COMMENT = re.compile(r"""
id(?P<details>[\dA-F]{4})(?P<address>[\dA-F]{6})\s
(?P<climb_rate>[+-]\d+)fpm\s
(?P<turn_rate>[+-][\d.]+)rot
""", re.VERBOSE | re.MULTILINE)
PATTERN_SKYLINES_POSITION_COMMENT = re.compile("""
id(?P<id>\d+)\s
PATTERN_SKYLINES_POSITION_COMMENT = re.compile(r"""
id(?P<skylines_id>\d+)\s
(?P<climb_rate>[+-]\d+)fpm
""", re.VERBOSE | re.MULTILINE)
PATTERN_SPIDER_POSITION_COMMENT = re.compile("""
id(?P<id>[\d-]+)\s
PATTERN_SPIDER_POSITION_COMMENT = re.compile(r"""
id(?P<spider_id>[\d-]+)\s
(?P<signal_power>[+-]\d+)dB\s
(?P<spider_id>[A-Z]+)\s
(?P<spider_registration>[A-Z0-9]+)\s
(?P<gps_quality>.+)
""", re.VERBOSE | re.MULTILINE)
PATTERN_SPOT_POSITION_COMMENT = re.compile("""
id(?P<id>[\d-]+)\s
SPOT(?P<model>\d)\s
PATTERN_SPOT_POSITION_COMMENT = re.compile(r"""
id(?P<spot_id>[\d-]+)\s
(?P<model>SPOT[A-Z\d]+)\s
(?P<status>[A-Z]+)
""", re.VERBOSE | re.MULTILINE)
PATTERN_TRACKER_POSITION_COMMENT = re.compile("""
PATTERN_INREACH_POSITION_COMMENT = re.compile(r"""
id(?P<id>[\d]+)\s
(?P<model>inReac[A-Za-z\d]*)\s
(?P<status>[A-Za-z]+)\s?
(?P<pilot_name>.+)?
""", re.VERBOSE | re.MULTILINE)
PATTERN_TRACKER_POSITION_COMMENT = re.compile(r"""
id(?P<details>[\dA-F]{2})(?P<address>[\dA-F]{6}?)\s?
(?:(?P<climb_rate>[+-]\d+?)fpm\s)?
(?:(?P<turn_rate>[+-][\d.]+?)rot\s)?
@ -67,24 +82,30 @@ PATTERN_TRACKER_POSITION_COMMENT = re.compile("""
(?:(?P<signal_power>[+-][\d.]+)dBm\s?)?
""", re.VERBOSE | re.MULTILINE)
PATTERN_TRACKER_STATUS_COMMENT = re.compile("""
PATTERN_SAFESKY_POSITION_COMMENT = re.compile(r"""
id(?P<details>[\dA-F]{2})(?P<address>[\dA-F]{6}?)\s?
(?:(?P<climb_rate>[+-]\d+?)fpm\s)?
(?:gps(?P<gps_quality>(?P<gps_quality_horizontal>(\d+))x(?P<gps_quality_vertical>(\d+)))?)?
""", re.VERBOSE | re.MULTILINE)
PATTERN_TRACKER_STATUS_COMMENT = re.compile(r"""
h(?P<hardware_version>[\d]{2})\s
v(?P<software_version>[\d]{2})\s
(?P<gps_satellites>[\d]+)sat/(?P<gps_quality>\d)\s
(?P<gps_altitude>\d+)m\s
(?P<pressure>[\d.]+)hPa\s
(?P<temperature>[+-][\d.]+)degC\s
(?P<humidity>\d+)%\s
(?P<voltage>[\d.]+)V\s
(?P<transmitter_power>\d+)/(?P<noise_level>[+-][\d.]+)dBm\s
(?P<relays>\d+)/min
v(?P<software_version>[\d]{2})\s?
(?:(?P<gps_satellites>[\d]+)sat/(?P<gps_quality>\d)\s?)?
(?:(?P<gps_altitude>\d+)m\s?)?
(?:(?P<pressure>[\d.]+)hPa\s?)?
(?:(?P<temperature>[+-][\d.]+)degC\s?)?
(?:(?P<humidity>\d+)%\s?)?
(?:(?P<voltage>[\d.]+)V\s?)?
(?:(?P<transmitter_power>\d+)/(?P<noise_level>[+-][\d.]+)dBm\s?)?
(?:(?P<relays>\d+)/min)?
""", re.VERBOSE | re.MULTILINE)
PATTERN_RECEIVER_POSITION_COMMENT = re.compile(r"""
(?:(?P<user_comment>.+))?
""", re.VERBOSE | re.MULTILINE)
PATTERN_RECEIVER_STATUS_COMMENT = re.compile("""
PATTERN_RECEIVER_STATUS_COMMENT = re.compile(r"""
(?:
v(?P<version>\d+\.\d+\.\d+)
(?:\.(?P<platform>.+?))?
@ -107,6 +128,30 @@ PATTERN_RECEIVER_STATUS_COMMENT = re.compile("""
)?
""", re.VERBOSE | re.MULTILINE)
PATTERN_TELNET_50001 = re.compile(r"""
(?P<pps_offset>\d\.\d+)sec:(?P<frequency>\d+\.\d+)MHz:\s+
(?P<aircraft_type>\d):(?P<address_type>\d):(?P<address>[A-F0-9]{6})\s
(?P<timestamp>\d{6}):\s
\[\s*(?P<latitude>[+-]\d+\.\d+),\s*(?P<longitude>[+-]\d+\.\d+)\]deg\s*
(?P<altitude>\d+)m\s*
(?P<climb_rate>[+-]\d+\.\d+)m/s\s*
(?P<ground_speed>\d+\.\d+)m/s\s*
(?P<track>\d+\.\d+)deg\s*
(?P<turn_rate>[+-]\d+\.\d+)deg/sec\s*
(?P<magic_number>\d+)\s*
(?P<gps_status>[0-9x]+)m\s*
(?P<channel>\d+)(?P<flarm_timeslot>[f_])(?P<ogn_timeslot>[o_])\s*
(?P<frequency_offset>[+-]\d+\.\d+)kHz\s*
(?P<decode_quality>\d+\.\d+)/(?P<signal_quality>\d+\.\d+)dB/(?P<demodulator_type>\d+)\s+
(?P<error_count>\d+)e\s*
(?P<distance>\d+\.\d+)km\s*
(?P<bearing>\d+\.\d+)deg\s*
(?P<phi>[+-]\d+\.\d+)deg\s*
(?P<multichannel>\+)?\s*
\?\s*
R?\s*
(B(?P<baro_altitude>\d+))?
""", re.VERBOSE | re.MULTILINE)
# The following regexp patterns are part of the ruby ogn-client.
# source: https://github.com/svoop/ogn_client-ruby
@ -140,11 +185,12 @@ PATTERN_RECEIVER_BEACON = re.compile(r"""
\s)?
CPU:(?P<cpu_load>[\d.]+)\s
RAM:(?P<ram_free>[\d.]+)/(?P<ram_total>[\d.]+)MB\s
NTP:(?P<ntp_offset>[\d.]+)ms/(?P<ntp_correction>[+-][\d.]+)ppm\s
NTP:(?P<ntp_offset>[\d.]+)ms/(?P<ntp_correction>[+-][\d.]+)ppm\s?
(?:(?P<voltage>[\d.]+)V\s)?
(?:(?P<amperage>[\d.]+)A\s)?
(?:(?P<cpu_temperature>[+-][\d.]+)C\s*)?
(?:(?P<visible_senders>\d+)/(?P<senders>\d+)Acfts\[1h\]\s*)?
(Lat\:(?P<latency>\d+\.\d+)s\s*)?
(?:RF:
(?:
(?P<rf_correction_manual>[+-][\d]+)

Wyświetl plik

@ -1,33 +1,42 @@
from datetime import datetime
from ogn.parser import ParseError
from ogn.parser.utils import createTimestamp
from ogn.parser.pattern import PATTERN_TELNET_50001
telnet_50001_pattern = PATTERN_TELNET_50001
def parse(telnet_data):
reference_timestamp = datetime.utcnow()
try:
return {'pps_offset': float(telnet_data[0:5]),
'frequency': float(telnet_data[9:16]),
'aircraft_type': int(telnet_data[20:24]),
'address_type': int(telnet_data[25]),
'address': telnet_data[27:33],
'timestamp': createTimestamp(telnet_data[34:40] + 'h', reference_timestamp),
'latitude': float(telnet_data[43:53]),
'longitude': float(telnet_data[54:64]),
'altitude': int(telnet_data[68:73]),
'climb_rate': float(telnet_data[74:80]),
'ground_speed': float(telnet_data[83:89]),
'track': float(telnet_data[92:98]),
'turn_rate': float(telnet_data[101:107]),
'magic_number': int(telnet_data[114:116]),
'gps_status': telnet_data[117:122],
'frequency_offset': float(telnet_data[123:129]),
'signal_quality': float(telnet_data[132:137]),
'error_count': float(telnet_data[139:142]),
'distance': float(telnet_data[143:150]),
'bearing': float(telnet_data[152:158]),
'phi': float(telnet_data[161:167])}
except:
raise ParseError
match = telnet_50001_pattern.match(telnet_data)
if match:
return {'pps_offset': float(match.group('pps_offset')),
'frequency': float(match.group('frequency')),
'aircraft_type': int(match.group('aircraft_type')),
'address_type': int(match.group('address_type')),
'address': match.group('address'),
'timestamp': createTimestamp(match.group('timestamp') + 'h', reference_timestamp),
'latitude': float(match.group('latitude')),
'longitude': float(match.group('longitude')),
'altitude': int(match.group('altitude')),
'climb_rate': float(match.group('climb_rate')),
'ground_speed': float(match.group('ground_speed')),
'track': float(match.group('track')),
'turn_rate': float(match.group('turn_rate')),
'magic_number': int(match.group('magic_number')),
'gps_status': match.group('gps_status'),
'channel': int(match.group('channel')),
'flarm_timeslot': match.group('flarm_timeslot') == 'f',
'ogn_timeslot': match.group('ogn_timeslot') == 'o',
'frequency_offset': float(match.group('frequency_offset')),
'decode_quality': float(match.group('decode_quality')),
'signal_quality': float(match.group('signal_quality')),
'demodulator_type': int(match.group('demodulator_type')),
'error_count': float(match.group('error_count')),
'distance': float(match.group('distance')),
'bearing': float(match.group('bearing')),
'phi': float(match.group('phi')),
'multichannel': match.group('multichannel') == '+'}
else:
return None

Wyświetl plik

@ -1,17 +1,23 @@
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
import math
FEETS_TO_METER = 0.3048 # ratio feets to meter
FPM_TO_MS = FEETS_TO_METER / 60 # ratio fpm to m/s
KNOTS_TO_MS = 0.5144 # ratio knots to m/s
KPH_TO_MS = 2.7778 # ratio kph to m/s
KPH_TO_MS = 0.27778 # ratio kph to m/s
HPM_TO_DEGS = 180 / 60 # ratio between half turn per minute and degrees/s
INCH_TO_MM = 25.4 # ratio inch to mm
def fahrenheit_to_celsius(fahrenheit):
return (fahrenheit - 32.0) * 5.0 / 9.0
def parseAngle(dddmmhht):
return float(dddmmhht[:3]) + float(dddmmhht[3:]) / 60
def createTimestamp(time_string, reference_timestamp=None):
def createTimestamp(time_string, reference_timestamp):
if time_string[-1] == "z":
dd = int(time_string[0:2])
hh = int(time_string[2:4])
@ -20,14 +26,14 @@ def createTimestamp(time_string, reference_timestamp=None):
result = datetime(reference_timestamp.year,
reference_timestamp.month,
dd,
hh, mm, 0)
hh, mm, 0,
tzinfo=timezone.utc if reference_timestamp.tzinfo is not None else None)
# correct wrong month
if result > reference_timestamp + timedelta(days=14):
# shift timestamp to previous month
result = (result.replace(day=1) - timedelta(days=5)).replace(day=result.day)
result = (result.replace(day=1) - timedelta(days=14)).replace(day=result.day)
elif result < reference_timestamp - timedelta(days=14):
# shift timestamp to next month
result = (result.replace(day=28) + timedelta(days=5)).replace(day=result.day)
result = (result.replace(day=28) + timedelta(days=14)).replace(day=result.day)
else:
hh = int(time_string[0:2])
mm = int(time_string[2:4])
@ -36,7 +42,8 @@ def createTimestamp(time_string, reference_timestamp=None):
result = datetime(reference_timestamp.year,
reference_timestamp.month,
reference_timestamp.day,
hh, mm, ss)
hh, mm, ss,
tzinfo=timezone.utc if reference_timestamp.tzinfo is not None else None)
if result > reference_timestamp + timedelta(hours=12):
# shift timestamp to previous day
@ -46,3 +53,43 @@ def createTimestamp(time_string, reference_timestamp=None):
result += timedelta(days=1)
return result
MATH_PI = 3.14159265359
class CheapRuler():
"""Extreme fast distance calculating for distances below 500km."""
def __init__(self, lat):
c = math.cos(lat * MATH_PI / 180)
c2 = 2 * c * c - 1
c3 = 2 * c * c2 - c
c4 = 2 * c * c3 - c2
c5 = 2 * c * c4 - c3
self.kx = 1000 * (111.41513 * c - 0.09455 * c3 + 0.00012 * c5) # longitude correction
self.ky = 1000 * (111.13209 - 0.56605 * c2 + 0.0012 * c4) # latitude correction
def distance(self, a, b):
"""Distance between point a and b. A point is a tuple(lon,lat)."""
dx = (a[0] - b[0]) * self.kx
dy = (a[1] - b[1]) * self.ky
return math.sqrt(dx * dx + dy * dy)
def bearing(self, a, b):
"""Returns the bearing from point a to point b."""
dx = (b[0] - a[0]) * self.kx
dy = (b[1] - a[1]) * self.ky
if dx == 0 and dy == 0:
return 0
result = math.atan2(-dy, dx) * 180 / MATH_PI + 90
return result if result >= 0 else result + 360
def normalized_quality(distance, signal_quality):
"""Signal quality normalized to 10km."""
return signal_quality + 20.0 * math.log10(distance / 10000.0) if distance > 0 else None

Wyświetl plik

@ -1,2 +1,2 @@
[flake8]
ignore = E501
ignore = E501,E701

Wyświetl plik

@ -17,30 +17,33 @@ setup(
version=PACKAGE_VERSION,
description='A python module for the Open Glider Network',
long_description=long_description,
long_description_content_type='text/markdown',
url='https://github.com/glidernet/python-ogn-client',
author='Konstantin Gründger aka Meisterschueler, Fabian P. Schmidt aka kerel',
author_email='kerel-fs@gmx.de',
license='AGPLv3',
classifiers=[
'Development Status :: 3 - Alpha',
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Intended Audience :: Science/Research',
'Topic :: Scientific/Engineering :: GIS',
'License :: OSI Approved :: GNU Affero General Public License v3',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9'
],
keywords='gliding ogn',
keywords=['gliding', 'ogn'],
packages=['ogn.{}'.format(package) for package in find_packages(where='ogn')],
python_requires='>=3',
install_requires=[],
extras_require={
'dev': [
'nose==1.3.7',
'coveralls==1.2',
'flake8==3.4.1'
'coveralls==3.3.1',
'flake8==4.0.1'
]
},
zip_safe=False

Wyświetl plik

@ -25,7 +25,7 @@ class AprsClientTest(unittest.TestCase):
client.connect()
client.sock.send.assert_called_once_with('user testuser pass -1 vers {} {}\n'.format(
APRS_APP_NAME, APRS_APP_VER).encode('ascii'))
client.sock.makefile.assert_called_once_with('rw')
client.sock.makefile.assert_called_once_with('rb')
@mock.patch('ogn.client.client.socket')
def test_connect_client_defined_filter(self, mock_socket):
@ -33,7 +33,7 @@ class AprsClientTest(unittest.TestCase):
client.connect()
client.sock.send.assert_called_once_with('user testuser pass -1 vers {} {} filter r/50.4976/9.9495/100\n'.format(
APRS_APP_NAME, APRS_APP_VER).encode('ascii'))
client.sock.makefile.assert_called_once_with('rw')
client.sock.makefile.assert_called_once_with('rb')
@mock.patch('ogn.client.client.socket')
def test_disconnect(self, mock_socket):
@ -53,16 +53,18 @@ class AprsClientTest(unittest.TestCase):
client.connect()
client.sock_file.readline = mock.MagicMock()
client.sock_file.readline.side_effect = ['Normal text blabla',
'my weird character ¥',
client.sock_file.readline.side_effect = [b'Normal text blabla',
b'my weird character \xc2\xa5',
UnicodeDecodeError('funnycodec', b'\x00\x00', 1, 2, 'This is just a fake reason!'),
'... show must go on',
b'... show must go on',
BrokenPipeError(),
'... and on',
b'... and on',
ConnectionResetError(),
b'... and on',
socket.error(),
'... and on',
'',
'... and on',
b'... and on',
b'',
b'... and on',
KeyboardInterrupt()]
try:
@ -82,7 +84,7 @@ class AprsClientTest(unittest.TestCase):
client.connect()
client.sock_file.readline = mock.MagicMock()
client.sock_file.readline.side_effect = ['Normal text blabla',
client.sock_file.readline.side_effect = [b'Normal text blabla',
KeyboardInterrupt()]
mock_time.side_effect = [0, 0, APRS_KEEPALIVE_TIME + 1, APRS_KEEPALIVE_TIME + 1]
@ -128,7 +130,7 @@ class AprsClientTest(unittest.TestCase):
return
try:
message = parse(raw_message)
print("{}: {}".format(message['beacon_type'], raw_message))
print("{}: {}".format(message['aprs_type'], raw_message))
except NotImplementedError as e:
print("{}: {}".format(e, raw_message))
return
@ -145,4 +147,4 @@ class AprsClientTest(unittest.TestCase):
pass
finally:
client.disconnect()
self.assert_(True)
self.assertTrue(True)

Wyświetl plik

@ -6,7 +6,7 @@ from datetime import datetime
from time import sleep
from ogn.parser.parse import parse
from ogn.parser.exceptions import AprsParseError, OgnParseError
from ogn.parser.exceptions import AprsParseError
class TestStringMethods(unittest.TestCase):
@ -39,12 +39,24 @@ class TestStringMethods(unittest.TestCase):
def test_tracker_beacons(self):
self.parse_valid_beacon_data_file(filename='tracker.txt', beacon_type='tracker')
def test_capturs_beacons(self):
self.parse_valid_beacon_data_file(filename='capturs.txt', beacon_type='capturs')
def test_flymaster_beacons(self):
self.parse_valid_beacon_data_file(filename='flymaster.txt', beacon_type='flymaster')
def test_inreach_beacons(self):
self.parse_valid_beacon_data_file(filename='inreach.txt', beacon_type='inreach')
def test_lt24_beacons(self):
self.parse_valid_beacon_data_file(filename='lt24.txt', beacon_type='lt24')
def test_naviter_beacons(self):
self.parse_valid_beacon_data_file(filename='naviter.txt', beacon_type='naviter')
def test_pilot_aware_beacons(self):
self.parse_valid_beacon_data_file(filename='pilot_aware.txt', beacon_type='pilot_aware')
def test_skylines_beacons(self):
self.parse_valid_beacon_data_file(filename='skylines.txt', beacon_type='skylines')
@ -54,6 +66,11 @@ class TestStringMethods(unittest.TestCase):
def test_spot_beacons(self):
self.parse_valid_beacon_data_file(filename='spot.txt', beacon_type='spot')
def test_generic_beacons(self):
message = parse("EPZR>WTFDSTCALL,TCPIP*,qAC,GLIDERN1:>093456h this is a comment")
self.assertEqual(message['beacon_type'], 'unknown')
self.assertEqual(message['comment'], "this is a comment")
def test_fail_parse_aprs_none(self):
with self.assertRaises(TypeError):
parse(None)
@ -66,10 +83,6 @@ class TestStringMethods(unittest.TestCase):
with self.assertRaises(AprsParseError):
parse("Lachens>APRS,TCPIwontbeavalidstring")
def test_fail_bad_dstcall(self):
with self.assertRaises(OgnParseError):
parse("EPZR>WTFDSTCALL,TCPIP*,qAC,GLIDERN1:>093456h this is a comment")
def test_v026_chile(self):
# receiver beacons from chile have a APRS position message with a pure user comment
message = parse("VITACURA1>APRS,TCPIP*,qAC,GLIDERN4:/201146h3322.79SI07034.80W&/A=002329 Vitacura Municipal Aerodrome, Club de Planeadores Vitacura")

Wyświetl plik

@ -2,7 +2,7 @@ import unittest
from datetime import datetime
from ogn.parser.utils import KNOTS_TO_MS, KPH_TO_MS, FEETS_TO_METER
from ogn.parser.utils import KNOTS_TO_MS, KPH_TO_MS, FEETS_TO_METER, INCH_TO_MM, fahrenheit_to_celsius
from ogn.parser.parse import parse_aprs
from ogn.parser.exceptions import AprsParseError
@ -14,6 +14,8 @@ class TestStringMethods(unittest.TestCase):
def test_basic(self):
message = parse_aprs("FLRDDA5BA>APRS,qAS,LFMX:/160829h4415.41N/00600.03E'342/049/A=005524 this is a comment")
self.assertEqual(message['aprs_type'], 'position')
self.assertEqual(message['name'], "FLRDDA5BA")
self.assertEqual(message['dstcall'], "APRS")
self.assertEqual(message['receiver_name'], "LFMX")
@ -27,13 +29,12 @@ class TestStringMethods(unittest.TestCase):
self.assertAlmostEqual(message['altitude'], 5524 * FEETS_TO_METER, 5)
self.assertEqual(message['comment'], "this is a comment")
self.assertEqual(message['aprs_type'], 'position')
def test_v024(self):
# higher precision datum format introduced
raw_message = "FLRDDA5BA>APRS,qAS,LFMX:/160829h4415.41N/00600.03E'342/049/A=005524 !W26! id21400EA9 -2454fpm +0.9rot 19.5dB 0e -6.6kHz gps1x1 s6.02 h44 rDF0C56"
message = parse_aprs(raw_message)
self.assertEqual(message['aprs_type'], 'position')
self.assertAlmostEqual(message['latitude'] - 44.2568 - 1 / 30000, 2 / 1000 / 60, 10)
self.assertAlmostEqual(message['longitude'] - 6.0005, 6 / 1000 / 60, 10)
@ -42,18 +43,18 @@ class TestStringMethods(unittest.TestCase):
raw_message = "EPZR>APRS,TCPIP*,qAC,GLIDERN1:>093456h this is a comment"
message = parse_aprs(raw_message)
self.assertEqual(message['aprs_type'], 'status')
self.assertEqual(message['name'], "EPZR")
self.assertEqual(message['receiver_name'], "GLIDERN1")
self.assertEqual(message['timestamp'].strftime('%H:%M:%S'), "09:34:56")
self.assertEqual(message['comment'], "this is a comment")
self.assertEqual(message['aprs_type'], 'status')
def test_v026(self):
# from 0.2.6 the ogn comment of a receiver beacon is just optional
raw_message = "Ulrichamn>APRS,TCPIP*,qAC,GLIDERN1:/085616h5747.30NI01324.77E&/A=001322"
message = parse_aprs(raw_message)
self.assertEqual(message['aprs_type'], 'position')
self.assertEqual(message['comment'], '')
def test_v026_relay(self):
@ -61,6 +62,7 @@ class TestStringMethods(unittest.TestCase):
raw_message = "FLRFFFFFF>OGNAVI,NAV07220E*,qAS,NAVITER:/092002h1000.00S/01000.00W'000/000/A=003281 !W00! id2820FFFFFF +300fpm +1.7rot"
message = parse_aprs(raw_message)
self.assertEqual(message['aprs_type'], 'position')
self.assertEqual(message['relay'], "NAV07220E")
def test_v027_ddhhmm(self):
@ -68,8 +70,44 @@ class TestStringMethods(unittest.TestCase):
raw_message = "ICA4B0678>APRS,qAS,LSZF:/301046z4729.50N/00812.89E'227/091/A=002854 !W01! id054B0678 +040fpm +0.0rot 19.0dB 0e +1.5kHz gps1x1"
message = parse_aprs(raw_message)
self.assertEqual(message['aprs_type'], 'position')
self.assertEqual(message['timestamp'].strftime('%d %H:%M'), "30 10:46")
def test_v028_fanet_position_weather(self):
# with v0.2.8 fanet devices can report weather data
raw_message = 'FNTFC9002>OGNFNT,qAS,LSXI2:/163051h4640.33N/00752.21E_187/004g007t075h78b63620 29.0dB -8.0kHz'
message = parse_aprs(raw_message)
self.assertEqual(message['aprs_type'], 'position_weather')
self.assertEqual(message['wind_direction'], 187)
self.assertEqual(message['wind_speed'], 4 * KNOTS_TO_MS / KPH_TO_MS)
self.assertEqual(message['wind_speed_peak'], 7 * KNOTS_TO_MS / KPH_TO_MS)
self.assertEqual(message['temperature'], fahrenheit_to_celsius(75))
self.assertEqual(message['humidity'], 78 * 0.01)
self.assertEqual(message['barometric_pressure'], 63620)
self.assertEqual(message['comment'], '29.0dB -8.0kHz')
def test_GXAirCom_fanet_position_weather_rainfall(self):
raw_message = 'FNT08F298>OGNFNT,qAS,DREIFBERG:/082654h4804.90N/00845.74E_273/005g008t057r123p234h90b10264 0.0dB'
message = parse_aprs(raw_message)
self.assertEqual(message['aprs_type'], 'position_weather')
self.assertEqual(message['rainfall_1h'], 123 / 100 * INCH_TO_MM)
self.assertEqual(message['rainfall_24h'], 234 / 100 * INCH_TO_MM)
def test_v028_fanet_position_weather_empty(self):
raw_message = 'FNT010115>OGNFNT,qAS,DB7MJ:/065738h4727.72N/01012.83E_.../...g...t... 27.8dB -13.8kHz'
message = parse_aprs(raw_message)
self.assertEqual(message['aprs_type'], 'position_weather')
self.assertIsNone(message['wind_direction'])
self.assertIsNone(message['wind_speed'])
self.assertIsNone(message['wind_speed_peak'])
self.assertIsNone(message['temperature'])
self.assertIsNone(message['humidity'])
self.assertIsNone(message['barometric_pressure'])
def test_negative_altitude(self):
# some devices can report negative altitudes
raw_message = "OGNF71F40>APRS,qAS,NAVITER:/080852h4414.37N/01532.06E'253/052/A=-00013 !W73! id1EF71F40 -060fpm +0.0rot"
@ -77,6 +115,13 @@ class TestStringMethods(unittest.TestCase):
self.assertAlmostEqual(message['altitude'], -13 * FEETS_TO_METER, 5)
def test_no_altitude(self):
# altitude is not a 'must have'
raw_message = "FLRDDEEF1>OGCAPT,qAS,CAPTURS:/065511h4837.63N/00233.79E'000/000"
message = parse_aprs(raw_message)
self.assertEqual(message['altitude'], None)
def test_invalid_coordinates(self):
# sometimes the coordinates leave their valid range: -90<=latitude<=90 or -180<=longitude<=180
with self.assertRaises(AprsParseError):

Wyświetl plik

@ -6,7 +6,7 @@ from ogn.parser.aprs_comment.fanet_parser import FanetParser
class TestStringMethods(unittest.TestCase):
def test_position_comment(self):
message = FanetParser.parse_position("id1E1103CE -02fpm")
message = FanetParser().parse_position("id1E1103CE -02fpm")
self.assertEqual(message['address_type'], 2)
self.assertEqual(message['aircraft_type'], 7)
@ -15,13 +15,17 @@ class TestStringMethods(unittest.TestCase):
self.assertAlmostEqual(message['climb_rate'], -2 * FPM_TO_MS, 0.1)
def test_pseudo_status_comment(self):
message = FanetParser.parse_position("")
message = FanetParser().parse_position("")
self.assertIsNone(message['address_type'])
self.assertIsNone(message['aircraft_type'])
self.assertIsNone(message['stealth'])
self.assertIsNone(message['address'])
self.assertIsNone(message['climb_rate'])
self.assertEqual(message, {})
def test_v028_status(self):
message = FanetParser().parse_status('Name="Juerg Zweifel" 15.0dB -17.1kHz 1e')
self.assertEqual(message['fanet_name'], "Juerg Zweifel")
self.assertEqual(message['signal_quality'], 15.0)
self.assertEqual(message['frequency_offset'], -17.1)
self.assertEqual(message['error_count'], 1)
if __name__ == '__main__':

Wyświetl plik

@ -6,11 +6,12 @@ from ogn.parser.aprs_comment.flarm_parser import FlarmParser
class TestStringMethods(unittest.TestCase):
def test_position_comment(self):
message = FlarmParser.parse_position("id21A8CBA8 -039fpm +0.1rot 3.5dB 2e -8.7kHz gps1x2 s6.09 h43 rDF0267")
message = FlarmParser().parse_position("id21A8CBA8 -039fpm +0.1rot 3.5dB 2e -8.7kHz gps1x2 s6.09 h43 rDF0267")
self.assertEqual(message['address_type'], 1)
self.assertEqual(message['aircraft_type'], 8)
self.assertFalse(message['stealth'])
self.assertFalse(message['no-tracking'])
self.assertEqual(message['address'], "A8CBA8")
self.assertAlmostEqual(message['climb_rate'], -39 * FPM_TO_MS, 2)
self.assertEqual(message['turn_rate'], 0.1 * HPM_TO_DEGS)
@ -22,6 +23,13 @@ class TestStringMethods(unittest.TestCase):
self.assertEqual(message['hardware_version'], 67)
self.assertEqual(message['real_address'], "DF0267")
def test_position_comment_relevant_keys_only(self):
# return only keys where we got informations
message = FlarmParser().parse_position("id21A8CBA8")
self.assertIsNotNone(message)
self.assertEqual(sorted(message.keys()), sorted(['address_type', 'aircraft_type', 'stealth', 'address', 'no-tracking']))
if __name__ == '__main__':
unittest.main()

Wyświetl plik

@ -0,0 +1,16 @@
import unittest
from ogn.parser.aprs_comment.generic_parser import GenericParser
class TestStringMethods(unittest.TestCase):
def test_position_comment(self):
message = GenericParser().parse_position("id0123456789 weather is good, climbing with 123fpm")
self.assertTrue('comment' in message)
message = GenericParser().parse_status("id0123456789 weather is good, climbing with 123fpm")
self.assertTrue('comment' in message)
if __name__ == '__main__':
unittest.main()

Wyświetl plik

@ -0,0 +1,21 @@
import unittest
from ogn.parser.aprs_comment.inreach_parser import InreachParser
class TestStringMethods(unittest.TestCase):
def test_position_comment(self):
message = InreachParser().parse_position("id300434060496190 inReac True")
self.assertEqual(message['address'], "300434060496190")
self.assertEqual(message['model'], 'inReac')
self.assertEqual(message['status'], True)
self.assertEqual(message['pilot_name'], None)
message = InreachParser().parse_position("id300434060496190 inReac True Jim Bob")
self.assertEqual(message['address'], "300434060496190")
self.assertEqual(message['model'], 'inReac')
self.assertEqual(message['status'], True)
self.assertEqual(message['pilot_name'], "Jim Bob")
if __name__ == '__main__':
unittest.main()

Wyświetl plik

@ -6,9 +6,9 @@ from ogn.parser.aprs_comment.lt24_parser import LT24Parser
class TestStringMethods(unittest.TestCase):
def test_position_comment(self):
message = LT24Parser.parse_position("id25387 +123fpm GPS")
message = LT24Parser().parse_position("id25387 +123fpm GPS")
self.assertEqual(message['address'], "25387")
self.assertEqual(message['lt24_id'], "25387")
self.assertAlmostEqual(message['climb_rate'], 123 * FPM_TO_MS, 2)
self.assertEqual(message['source'], 'GPS')

Wyświetl plik

@ -6,7 +6,7 @@ from ogn.parser.aprs_comment.naviter_parser import NaviterParser
class TestStringMethods(unittest.TestCase):
def test_OGNAVI_1(self):
message = NaviterParser.parse_position("id0440042121 +123fpm +0.5rot")
message = NaviterParser().parse_position("id0440042121 +123fpm +0.5rot")
# id0440042121 == 0b0000 0100 0100 0000 0000 0100 0010 0001 0010 0001
# bit 0: stealth mode

Wyświetl plik

@ -6,14 +6,15 @@ from ogn.parser.aprs_comment.ogn_parser import OgnParser
class TestStringMethods(unittest.TestCase):
def test_invalid_token(self):
self.assertEqual(OgnParser.parse_aircraft_beacon("notAValidToken"), None)
self.assertEqual(OgnParser().parse_aircraft_beacon("notAValidToken"), None)
def test_basic(self):
message = OgnParser.parse_aircraft_beacon("id0ADDA5BA -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
message = OgnParser().parse_aircraft_beacon("id0ADDA5BA -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
self.assertEqual(message['address_type'], 2)
self.assertEqual(message['aircraft_type'], 2)
self.assertFalse(message['stealth'])
self.assertFalse(message['no-tracking'])
self.assertEqual(message['address'], "DDA5BA")
self.assertAlmostEqual(message['climb_rate'], -454 * FPM_TO_MS, 2)
self.assertEqual(message['turn_rate'], -1.1 * HPM_TO_DEGS)
@ -26,38 +27,52 @@ class TestStringMethods(unittest.TestCase):
self.assertEqual(message['proximity'][1], 'B597')
self.assertEqual(message['proximity'][2], 'B598')
def test_no_tracking(self):
message = OgnParser().parse_aircraft_beacon("id0ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
self.assertFalse(message['no-tracking'])
message = OgnParser().parse_aircraft_beacon("id4ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
self.assertTrue(message['no-tracking'])
def test_stealth(self):
message = OgnParser.parse_aircraft_beacon("id0ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
message = OgnParser().parse_aircraft_beacon("id0ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
self.assertFalse(message['stealth'])
message = OgnParser.parse_aircraft_beacon("id8ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
message = OgnParser().parse_aircraft_beacon("id8ADD1234 -454fpm -1.1rot 8.8dB 0e +51.2kHz gps4x5 hear1084 hearB597 hearB598")
self.assertTrue(message['stealth'])
def test_v024(self):
message = OgnParser.parse_aircraft_beacon("id21400EA9 -2454fpm +0.9rot 19.5dB 0e -6.6kHz gps1x1 s6.02 h0A rDF0C56")
message = OgnParser().parse_aircraft_beacon("id21400EA9 -2454fpm +0.9rot 19.5dB 0e -6.6kHz gps1x1 s6.02 h0A rDF0C56")
self.assertEqual(message['software_version'], 6.02)
self.assertEqual(message['hardware_version'], 10)
self.assertEqual(message['real_address'], "DF0C56")
def test_v024_ogn_tracker(self):
message = OgnParser.parse_aircraft_beacon("id07353800 +020fpm -14.0rot FL004.43 38.5dB 0e -2.9kHz")
message = OgnParser().parse_aircraft_beacon("id07353800 +020fpm -14.0rot FL004.43 38.5dB 0e -2.9kHz")
self.assertEqual(message['flightlevel'], 4.43)
def test_v025(self):
message = OgnParser.parse_aircraft_beacon("id06DDE28D +535fpm +3.8rot 11.5dB 0e -1.0kHz gps2x3 s6.01 h0C +7.4dBm")
message = OgnParser().parse_aircraft_beacon("id06DDE28D +535fpm +3.8rot 11.5dB 0e -1.0kHz gps2x3 s6.01 h0C +7.4dBm")
self.assertEqual(message['signal_power'], 7.4)
def test_v026(self):
# from 0.2.6 it is sufficent we have only the ID, climb and turn rate or just the ID
message_triple = OgnParser.parse_aircraft_beacon("id093D0930 +000fpm +0.0rot")
message_single = OgnParser.parse_aircraft_beacon("id093D0930")
message_triple = OgnParser().parse_aircraft_beacon("id093D0930 +000fpm +0.0rot")
message_single = OgnParser().parse_aircraft_beacon("id093D0930")
self.assertIsNotNone(message_triple)
self.assertIsNotNone(message_single)
def test_relevant_keys_only(self):
# return only keys where we got informations
message = OgnParser().parse_aircraft_beacon("id093D0930")
self.assertIsNotNone(message)
self.assertEqual(sorted(message.keys()), sorted(['address_type', 'aircraft_type', 'stealth', 'address', 'no-tracking']))
if __name__ == '__main__':
unittest.main()

Wyświetl plik

@ -5,10 +5,10 @@ from ogn.parser.aprs_comment.ogn_parser import OgnParser
class TestStringMethods(unittest.TestCase):
def test_fail_validation(self):
self.assertEqual(OgnParser.parse_receiver_beacon("notAValidToken"), None)
self.assertEqual(OgnParser().parse_receiver_beacon("notAValidToken"), None)
def test_v021(self):
message = OgnParser.parse_receiver_beacon("v0.2.1 CPU:0.8 RAM:25.6/458.9MB NTP:0.1ms/+2.3ppm +51.9C RF:+26-1.4ppm/-0.25dB")
message = OgnParser().parse_receiver_beacon("v0.2.1 CPU:0.8 RAM:25.6/458.9MB NTP:0.1ms/+2.3ppm +51.9C RF:+26-1.4ppm/-0.25dB")
self.assertEqual(message['version'], "0.2.1")
self.assertEqual(message['cpu_load'], 0.8)
@ -23,17 +23,17 @@ class TestStringMethods(unittest.TestCase):
self.assertEqual(message['rec_input_noise'], -0.25)
def test_v022(self):
message = OgnParser.parse_receiver_beacon("v0.2.2.x86 CPU:0.5 RAM:669.9/887.7MB NTP:1.0ms/+6.2ppm +52.0C RF:+0.06dB")
message = OgnParser().parse_receiver_beacon("v0.2.2.x86 CPU:0.5 RAM:669.9/887.7MB NTP:1.0ms/+6.2ppm +52.0C RF:+0.06dB")
self.assertEqual(message['platform'], 'x86')
def test_v025(self):
message = OgnParser.parse_receiver_beacon("v0.2.5.RPI-GPU CPU:0.8 RAM:287.3/458.7MB NTP:1.0ms/-6.4ppm 5.016V 0.534A +51.9C RF:+55+0.4ppm/-0.67dB/+10.8dB@10km[57282]")
message = OgnParser().parse_receiver_beacon("v0.2.5.RPI-GPU CPU:0.8 RAM:287.3/458.7MB NTP:1.0ms/-6.4ppm 5.016V 0.534A +51.9C RF:+55+0.4ppm/-0.67dB/+10.8dB@10km[57282]")
self.assertEqual(message['voltage'], 5.016)
self.assertEqual(message['amperage'], 0.534)
self.assertEqual(message['senders_signal'], 10.8)
self.assertEqual(message['senders_messages'], 57282)
message = OgnParser.parse_receiver_beacon("v0.2.5.ARM CPU:0.4 RAM:638.0/970.5MB NTP:0.2ms/-1.1ppm +65.5C 14/16Acfts[1h] RF:+45+0.0ppm/+3.88dB/+24.0dB@10km[143717]/+26.7dB@10km[68/135]")
message = OgnParser().parse_receiver_beacon("v0.2.5.ARM CPU:0.4 RAM:638.0/970.5MB NTP:0.2ms/-1.1ppm +65.5C 14/16Acfts[1h] RF:+45+0.0ppm/+3.88dB/+24.0dB@10km[143717]/+26.7dB@10km[68/135]")
self.assertEqual(message['senders_visible'], 14)
self.assertEqual(message['senders_total'], 16)
self.assertEqual(message['senders_signal'], 24.0)
@ -42,6 +42,17 @@ class TestStringMethods(unittest.TestCase):
self.assertEqual(message['good_senders'], 68)
self.assertEqual(message['good_and_bad_senders'], 135)
def test_v028(self):
message = OgnParser().parse_receiver_beacon("v0.2.8.RPI-GPU CPU:0.3 RAM:744.5/968.2MB NTP:3.6ms/+2.0ppm +68.2C 3/3Acfts[1h] Lat:1.6s RF:-8+67.8ppm/+10.33dB/+1.3dB@10km[30998]/+10.4dB@10km[3/5]")
self.assertEqual(message['latency'], 1.6)
def test_relevant_keys_only(self):
# return only keys where we got informations
message = OgnParser().parse_receiver_beacon("v0.2.5.ARM CPU:0.4 RAM:638.0/970.5MB NTP:0.2ms/-1.1ppm")
self.assertIsNotNone(message)
self.assertEqual(sorted(message.keys()), sorted(['version', 'platform', 'cpu_load', 'free_ram', 'total_ram', 'ntp_error', 'rt_crystal_correction']))
if __name__ == '__main__':
unittest.main()

Wyświetl plik

@ -5,17 +5,17 @@ from ogn.parser.aprs_comment.receiver_parser import ReceiverParser
class TestStringMethods(unittest.TestCase):
def test_position_comment(self):
message = ReceiverParser.parse_position("Antenna: chinese, on a pylon, 20 meter above ground")
message = ReceiverParser().parse_position("Antenna: chinese, on a pylon, 20 meter above ground")
self.assertEqual(message['user_comment'], "Antenna: chinese, on a pylon, 20 meter above ground")
def test_position_comment_empty(self):
message = ReceiverParser.parse_position("")
message = ReceiverParser().parse_position("")
self.assertIsNotNone(message)
def test_status_comment(self):
message = ReceiverParser.parse_status("v0.2.7.RPI-GPU CPU:0.7 RAM:770.2/968.2MB NTP:1.8ms/-3.3ppm +55.7C 7/8Acfts[1h] RF:+54-1.1ppm/-0.16dB/+7.1dB@10km[19481]/+16.8dB@10km[7/13]")
message = ReceiverParser().parse_status("v0.2.7.RPI-GPU CPU:0.7 RAM:770.2/968.2MB NTP:1.8ms/-3.3ppm +55.7C 7/8Acfts[1h] RF:+54-1.1ppm/-0.16dB/+7.1dB@10km[19481]/+16.8dB@10km[7/13]")
self.assertEqual(message['version'], "0.2.7")
self.assertEqual(message['platform'], 'RPI-GPU')

Wyświetl plik

@ -0,0 +1,20 @@
import unittest
from ogn.parser.utils import FPM_TO_MS
from ogn.parser.aprs_comment.safesky_parser import SafeskyParser
class TestStringMethods(unittest.TestCase):
def test_position_comment(self):
# "SKY3E5906>OGNSKY,qAS,SafeSky:/072555h5103.47N/00524.81E'065/031/A=001250 !W05! id1C3E5906 +010fpm gps6x1"
message = SafeskyParser().parse_position("id1C3E5906 +010fpm gps6x1")
self.assertEqual(message['address'], '3E5906')
self.assertEqual(message['address_type'], 0)
self.assertEqual(message['aircraft_type'], 7)
self.assertFalse(message['stealth'])
self.assertAlmostEqual(message['climb_rate'], 10 * FPM_TO_MS, 2)
self.assertEqual(message['gps_quality'], {'horizontal': 6, 'vertical': 1})
if __name__ == '__main__':
unittest.main()

Wyświetl plik

@ -6,9 +6,9 @@ from ogn.parser.aprs_comment.skylines_parser import SkylinesParser
class TestStringMethods(unittest.TestCase):
def test_position_comment(self):
message = SkylinesParser.parse_position("id2816 -015fpm")
message = SkylinesParser().parse_position("id2816 -015fpm")
self.assertEqual(message['address'], "2816")
self.assertEqual(message['skylines_id'], "2816")
self.assertAlmostEqual(message['climb_rate'], -15 * FPM_TO_MS, 2)

Wyświetl plik

@ -5,11 +5,11 @@ from ogn.parser.aprs_comment.spider_parser import SpiderParser
class TestStringMethods(unittest.TestCase):
def test_position_comment(self):
message = SpiderParser.parse_position("id300234010617040 +19dB LWE 3D")
message = SpiderParser().parse_position("id300234060668560 +30dB K23W 3D")
self.assertEqual(message['address'], "300234010617040")
self.assertEqual(message['signal_power'], 19)
self.assertEqual(message['spider_id'], "LWE")
self.assertEqual(message['spider_id'], "300234060668560")
self.assertEqual(message['signal_power'], 30)
self.assertEqual(message['spider_registration'], "K23W")
self.assertEqual(message['gps_quality'], "3D")

Wyświetl plik

@ -5,10 +5,10 @@ from ogn.parser.aprs_comment.spot_parser import SpotParser
class TestStringMethods(unittest.TestCase):
def test_position_comment(self):
message = SpotParser.parse_position("id0-2860357 SPOT3 GOOD")
message = SpotParser().parse_position("id0-2860357 SPOT3 GOOD")
self.assertEqual(message['address'], "0-2860357")
self.assertEqual(message['model'], 3)
self.assertEqual(message['spot_id'], "0-2860357")
self.assertEqual(message['model'], 'SPOT3')
self.assertEqual(message['status'], "GOOD")

Wyświetl plik

@ -14,33 +14,44 @@ class TestStringMethods(unittest.TestCase):
parse('This is rubbish')
@mock.patch('ogn.parser.telnet_parser.datetime')
def test_telnet_parse(self, datetime_mock):
def test_telnet_parse_complete(self, datetime_mock):
# set the utcnow-mock near to the time in the test string
datetime_mock.utcnow.return_value = datetime(2015, 1, 1, 10, 0, 55)
message = parse('0.867sec:868.398MHz: 1:2:DD9AB2 100050: [ +47.44356, +11.77856]deg 2749m +3.2m/s 34.5m/s 082.7deg -0.7deg/sec 5 03x03m -1.6kHz 8.5dB 1e 50.6km 151.4deg +1.9deg')
message = parse('0.181sec:868.394MHz: 1:2:DDA411 103010: [ +50.86800, +12.15279]deg 988m +0.1m/s 25.7m/s 085.4deg -3.5deg/sec 5 03x04m 01f_-12.61kHz 5.8/15.5dB/2 10e 30.9km 099.5deg +1.1deg + ? R B8949')
self.assertEqual(message['pps_offset'], 0.867)
self.assertEqual(message['frequency'], 868.398)
self.assertEqual(message['pps_offset'], 0.181)
self.assertEqual(message['frequency'], 868.394)
self.assertEqual(message['aircraft_type'], 1)
self.assertEqual(message['address_type'], 2)
self.assertEqual(message['address'], 'DD9AB2')
self.assertEqual(message['timestamp'], datetime(2015, 1, 1, 10, 0, 50))
self.assertEqual(message['latitude'], 47.44356)
self.assertEqual(message['longitude'], 11.77856)
self.assertEqual(message['altitude'], 2749)
self.assertEqual(message['climb_rate'], 3.2)
self.assertEqual(message['ground_speed'], 34.5)
self.assertEqual(message['track'], 82.7)
self.assertEqual(message['turn_rate'], -0.7)
self.assertEqual(message['address'], 'DDA411')
self.assertEqual(message['timestamp'], datetime(2015, 1, 1, 10, 30, 10))
self.assertEqual(message['latitude'], 50.868)
self.assertEqual(message['longitude'], 12.15279)
self.assertEqual(message['altitude'], 988)
self.assertEqual(message['climb_rate'], 0.1)
self.assertEqual(message['ground_speed'], 25.7)
self.assertEqual(message['track'], 85.4)
self.assertEqual(message['turn_rate'], -3.5)
self.assertEqual(message['magic_number'], 5) # the '5' is a magic number... 1 if ground_speed is 0.0m/s an 3 or 5 if airborne. Do you have an idea what it is?
self.assertEqual(message['gps_status'], '03x03')
self.assertEqual(message['frequency_offset'], -1.6)
self.assertEqual(message['signal_quality'], 8.5)
self.assertEqual(message['error_count'], 1)
self.assertEqual(message['distance'], 50.6)
self.assertEqual(message['bearing'], 151.4)
self.assertEqual(message['phi'], 1.9)
self.assertEqual(message['gps_status'], '03x04')
self.assertEqual(message['channel'], 1)
self.assertEqual(message['flarm_timeslot'], True)
self.assertEqual(message['ogn_timeslot'], False)
self.assertEqual(message['frequency_offset'], -12.61)
self.assertEqual(message['decode_quality'], 5.8)
self.assertEqual(message['signal_quality'], 15.5)
self.assertEqual(message['demodulator_type'], 2)
self.assertEqual(message['error_count'], 10)
self.assertEqual(message['distance'], 30.9)
self.assertEqual(message['bearing'], 99.5)
self.assertEqual(message['phi'], 1.1)
self.assertEqual(message['multichannel'], True)
def test_telnet_parse_corrupt(self):
message = parse('0.397sec:868.407MHz: sA:1:784024 205656: [ +5.71003, +20.48951]deg 34012m +14.5m/s 109.7m/s 118.5deg +21.0deg/sec 0 27x40m 01_o +7.03kHz 17.2/27.0dB/2 12e 4719.5km 271.1deg -8.5deg ? R B34067')
self.assertIsNone(message)
if __name__ == '__main__':

Wyświetl plik

@ -6,7 +6,7 @@ from ogn.parser.aprs_comment.tracker_parser import TrackerParser
class TestStringMethods(unittest.TestCase):
def test_position_comment(self):
message = TrackerParser.parse_position("id072FD00F -058fpm +1.1rot FL003.12 32.8dB 0e -0.8kHz gps3x5 +12.7dBm")
message = TrackerParser().parse_position("id072FD00F -058fpm +1.1rot FL003.12 32.8dB 0e -0.8kHz gps3x5 +12.7dBm")
self.assertEqual(message['address_type'], 3)
self.assertEqual(message['aircraft_type'], 1)
@ -22,7 +22,7 @@ class TestStringMethods(unittest.TestCase):
self.assertEqual(message['signal_power'], 12.7)
def test_status_comment(self):
message = TrackerParser.parse_status("h00 v00 9sat/1 164m 1002.6hPa +20.2degC 0% 3.34V 14/-110.5dBm 1/min")
message = TrackerParser().parse_status("h00 v00 9sat/1 164m 1002.6hPa +20.2degC 0% 3.34V 14/-110.5dBm 1/min")
self.assertEqual(message['hardware_version'], 0)
self.assertEqual(message['software_version'], 0)
@ -37,6 +37,11 @@ class TestStringMethods(unittest.TestCase):
self.assertEqual(message['noise_level'], -110.5)
self.assertEqual(message['relays'], 1)
def test_status_comment_comment(self):
message = TrackerParser().parse_status("Pilot=Pawel Hard=DIY/STM32")
self.assertEqual(message['comment'], "Pilot=Pawel Hard=DIY/STM32")
if __name__ == '__main__':
unittest.main()

Wyświetl plik

@ -1,7 +1,7 @@
import unittest
from datetime import datetime
from datetime import datetime, timezone
from ogn.parser.utils import parseAngle, createTimestamp
from ogn.parser.utils import parseAngle, createTimestamp, CheapRuler, normalized_quality
class TestStringMethods(unittest.TestCase):
@ -33,6 +33,34 @@ class TestStringMethods(unittest.TestCase):
self.proceed_test_data(test_data)
def test_createTimestamp_tzinfo(self):
test_data = [
('000001h', datetime(2020, 9, 10, 0, 0, 1, tzinfo=timezone.utc), (datetime(2020, 9, 10, 0, 0, 1, tzinfo=timezone.utc)))
]
self.proceed_test_data(test_data)
def test_cheap_ruler_distance(self):
koenigsdf = (11.465353, 47.829825)
hochkoenig = (13.062405, 47.420516)
cheap_ruler = CheapRuler((koenigsdf[1] + hochkoenig[1]) / 2)
distance = cheap_ruler.distance(koenigsdf, hochkoenig)
self.assertAlmostEqual(distance, 128381.47612138899)
def test_cheap_ruler_bearing(self):
koenigsdf = (11.465353, 47.829825)
hochkoenig = (13.062405, 47.420516)
cheap_ruler = CheapRuler((koenigsdf[1] + hochkoenig[1]) / 2)
bearing = cheap_ruler.bearing(koenigsdf, hochkoenig)
self.assertAlmostEqual(bearing, 110.761300063515)
def test_normalized_quality(self):
self.assertAlmostEqual(normalized_quality(10000, 1), 1)
self.assertAlmostEqual(normalized_quality(20000, 10), 16.020599913279625)
self.assertAlmostEqual(normalized_quality(5000, 5), -1.0205999132796242)
if __name__ == '__main__':
unittest.main()

Wyświetl plik

@ -0,0 +1,11 @@
# The following beacons are example for the Capture APRS format
# source: https://github.com/glidernet/ogn-aprs-protocol
#
FLRDDEEF1>OGCAPT,qAS,CAPTURS:/062744h4845.03N/00230.46E'000/000
FLRDDEEF1>OGCAPT,qAS,CAPTURS:/064243h4839.64N/00236.78E'000/085/A=000410
FLRDDEEF1>OGCAPT,qAS,CAPTURS:/064548h4838.87N/00234.03E'000/042/A=000377
FLRDDEEF1>OGCAPT,qAS,CAPTURS:/064847h4837.95N/00234.36E'000/000
FLRDDEEF1>OGCAPT,qAS,CAPTURS:/065144h4837.56N/00233.80E'000/000
FLRDDEEF1>OGCAPT,qAS,CAPTURS:/065511h4837.63N/00233.79E'000/000
FLRDDEEF1>OGCAPT,qAS,CAPTURS:/070016h4837.63N/00233.77E'000/001/A=000360
FLRDDEEF1>OGCAPT,qAS,CAPTURS:/070153h4837.62N/00233.77E'000/001/A=000344

Wyświetl plik

@ -6,3 +6,14 @@ FNT1103CE>OGNFNT,qAS,FNB1103CE:/183731h5057.94N/00801.00Eg354/001/A=001042 !W10!
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
FNB1103CE>OGNFNT,TCPIP*,qAC,GLIDERN3:/183738h5057.95NI00801.00E&/A=001042
#
# With OGN software 0.2.8 we get weather data in the position message ...
#
FNTFC9002>OGNFNT,qAS,LSXI2:/163051h4640.33N/00752.21E_187/004g007t075h78b63620 29.0dB -8.0kHz
FNT051015>OGNFNT,qAS,LSXI2:/112540h4641.18N/00751.53E_097/007g008t082h54 28.8dB -8.3kHz
#
# ... and additional fanet data in the status message
#
FNT1122AE>OGNFNT,qAS,LIDH:>112528h Name="Juerg Zweifel" 15.0dB -17.1kHz 1e
FNT0728B8>OGNFNT,qAS,Huenenbg:>112533h Name="Huenenb2" 26.8dB +3.0kHz 6e
FNT111369>OGNFNT,qAS,LSXI2:>112535h Name="Zaugg Thomas" 18.3dB -15.4kHz

Wyświetl plik

@ -4,4 +4,5 @@ FLRDD89C9>OGFLR,qAS,LIDH:/115054h4543.22N/01132.84E'260/072/A=002542 !W10! id06D
FLRDD98C6>OGFLR,qAS,LIDH:/115054h4543.21N/01132.80E'255/074/A=002535 !W83! id0ADD98C6 +158fpm -1.8rot 10.5dB 0e -0.8kHz gps2x3 s6.09 h02
ICAA8CBA8>OGFLR,qAS,MontCAIO:/231150z4512.12N\01059.03E^192/106/A=009519 !W20! id21A8CBA8 -039fpm +0.0rot 3.5dB 2e -8.7kHz gps1x2 s6.09 h43 rDF0267
ICAA8CBA8>OGFLR,qAS,MontCAIO:/114949h4512.44N\01059.12E^190/106/A=009522 !W33! id21A8CBA8 -039fpm +0.1rot 4.5dB 1e -8.7kHz gps1x2 +14.3dBm
ICA3D1C35>OGFLR,qAS,Padova:/094220h4552.41N/01202.28E'110/099/A=003982 !W96! id053D1C35 -1187fpm +0.0rot 0.8dB 2e +4.5kHz gps1x2 s6.09 h32 rDD09D0
ICA3D1C35>OGFLR,qAS,Padova:/094220h4552.41N/01202.28E'110/099/A=003982 !W96! id053D1C35 -1187fpm +0.0rot 0.8dB 2e +4.5kHz gps1x2 s6.09 h32 rDD09D0
FLR200295>OGFLR,qAS,TT:/071005h4613.92N/01427.53Eg000/000/A=001313 !W00! id1E200295 +000fpm +0.0rot 37.0dB -1.8kHz gps3x5

Wyświetl plik

@ -0,0 +1,35 @@
# The following beacons are example for Flymaster APRS format
# source: https://github.com/glidernet/ogn-aprs-protocol
#
FMT924469>OGFLYM,qAS,FLYMASTER:/155232h3720.70N/00557.97W^222/092/A=000029 !W52!
FMT003549>OGFLYM,qAS,FLYMASTER:/155231h3751.35N/00126.13W^270/022/A=001430 !W14!
FMT001300>OGFLYM,qAS,FLYMASTER:/155249h3706.99N/00807.27W^178/000/A=000131 !W86!
FMT798890>OGFLYM,qAS,FLYMASTER:/155256h3720.49N/00558.27W^234/086/A=000009 !W00!
FMT549112>OGFLYM,qAS,FLYMASTER:/155256h3720.48N/00558.27W^234/086/A=000032 !W81!
FMT148694>OGFLYM,qAS,FLYMASTER:/155244h3720.58N/00558.11W^226/087/A=000019 !W81!
FMT842374>OGFLYM,qAS,FLYMASTER:/155302h3720.44N/00558.34W^236/082/A=000013 !W88!
FMT003725>OGFLYM,qAS,FLYMASTER:/155304h3652.58N/00255.91W^346/000/A=001968 !W66!
FMT924469>OGFLYM,qAS,FLYMASTER:/155306h3720.42N/00558.40W^250/081/A=000013 !W85!
FMT003549>OGFLYM,qAS,FLYMASTER:/155316h3751.64N/00126.08W^020/048/A=001322 !W98!
FMT148694>OGFLYM,qAS,FLYMASTER:/155318h3720.42N/00558.59W^282/088/A=000026 !W85!
FMT549112>OGFLYM,qAS,FLYMASTER:/155328h3720.45N/00558.75W^282/079/A=000032 !W60!
FMT842374>OGFLYM,qAS,FLYMASTER:/155335h3720.47N/00558.84W^280/078/A=000019 !W68!
FMT001300>OGFLYM,qAS,FLYMASTER:/155339h3706.99N/00807.27W^178/000/A=000131 !W95!
FMT798890>OGFLYM,qAS,FLYMASTER:/155338h3720.48N/00558.89W^282/080/A=000019 !W46!
FMT924469>OGFLYM,qAS,FLYMASTER:/155341h3720.49N/00558.93W^282/075/A=000009 !W27!
FMT003725>OGFLYM,qAS,FLYMASTER:/155346h3652.58N/00255.91W^346/000/A=001971 !W75!
FMT003549>OGFLYM,qAS,FLYMASTER:/155349h3751.76N/00125.91W^064/032/A=001414 !W27!
FMT148694>OGFLYM,qAS,FLYMASTER:/155352h3720.51N/00559.02W^292/026/A=000026 !W48!
FMT549112>OGFLYM,qAS,FLYMASTER:/155400h3720.52N/00559.06W^298/031/A=000045 !W74!
FMT842374>OGFLYM,qAS,FLYMASTER:/155409h3720.54N/00559.10W^302/019/A=000042 !W70!
FMT798890>OGFLYM,qAS,FLYMASTER:/155412h3720.54N/00559.10W^304/001/A=000026 !W96!
FMT924469>OGFLYM,qAS,FLYMASTER:/155415h3720.54N/00559.10W^000/001/A=000022 !W95!
FMT003725>OGFLYM,qAS,FLYMASTER:/155420h3652.58N/00255.91W^346/000/A=001971 !W75!
FMT003549>OGFLYM,qAS,FLYMASTER:/155422h3751.81N/00125.73W^220/002/A=001584 !W42!
FMT001300>OGFLYM,qAS,FLYMASTER:/155429h3706.99N/00807.27W^178/000/A=000131 !W96!
FMT148694>OGFLYM,qAS,FLYMASTER:/155435h3720.58N/00559.16W^314/017/A=000039 !W83!
FMT549112>OGFLYM,qAS,FLYMASTER:/155443h3720.59N/00559.16W^000/000/A=000065 !W18!
FMT798890>OGFLYM,qAS,FLYMASTER:/155444h3720.59N/00559.16W^000/000/A=000039 !W29!
FMT924469>OGFLYM,qAS,FLYMASTER:/155447h3720.59N/00559.16W^000/000/A=000039 !W28!
FMT842374>OGFLYM,qAS,FLYMASTER:/155453h3720.60N/00559.17W^316/020/A=000055 !W07!
FMT003549>OGFLYM,qAS,FLYMASTER:/155455h3751.82N/00125.81W^248/012/A=001676 !W99!

Wyświetl plik

@ -0,0 +1,4 @@
# The following beacons are example for Garmin inReach APRS format
# source: https://github.com/glidernet/ogn-aprs-protocol
#
OGN8A0749>OGINRE,qAS,INREACH:/142700h0448.38N/07600.74W'000/000/A=004583 id300434060496190 inReac True

Wyświetl plik

@ -0,0 +1,5 @@
# The following beacons are example for PilotAware's APRS format version OGPAW-1
# source: https://github.com/glidernet/ogn-aprs-protocol
#
ICA404EC3>OGPAW,qAS,UKWOG:/104337h5211.24N\00032.65W^124/081/A=004026 !W62! id21404EC3 12.5dB +2.2kHz
ICA404EC3>OGPAW,qAS,UKWOG:/104341h5211.18N\00032.53W^131/081/A=004010 !W85! id21404EC3 9.2dB +2.2kHz +10.0dBm

Wyświetl plik

@ -13,4 +13,8 @@ LZHL>OGNSDR,TCPIP*,qAC,GLIDERN3:>132457h v0.2.7.arm CPU:0.9 RAM:75.3/253.6MB NTP
BELG>OGNSDR,TCPIP*,qAC,GLIDERN3:/132507h4509.60NI00919.20E&/A=000246
BELG>OGNSDR,TCPIP*,qAC,GLIDERN3:>132507h v0.2.7.RPI-GPU CPU:1.2 RAM:35.7/455.2MB NTP:2.5ms/-5.3ppm +67.0C 1/1Acfts[1h] RF:+79+8.8ppm/+4.97dB/-0.0dB@10km[299]/+4.9dB@10km[2/3]
Saleve>OGNSDR,TCPIP*,qAC,GLIDERN1:/132624h4607.70NI00610.41E&/A=004198 Antenna: chinese, on a pylon, 20 meter above ground
Saleve>OGNSDR,TCPIP*,qAC,GLIDERN1:>132624h v0.2.7.arm CPU:1.7 RAM:812.3/1022.5MB NTP:1.8ms/+4.5ppm 0.000V 0.000A 3/4Acfts[1h] RF:+67+2.9ppm/+4.18dB/+11.7dB@10km[5018]/+17.2dB@10km[8/16]
Saleve>OGNSDR,TCPIP*,qAC,GLIDERN1:>132624h v0.2.7.arm CPU:1.7 RAM:812.3/1022.5MB NTP:1.8ms/+4.5ppm 0.000V 0.000A 3/4Acfts[1h] RF:+67+2.9ppm/+4.18dB/+11.7dB@10km[5018]/+17.2dB@10km[8/16]
#
# With OGN software 0.2.8 we got the latency
#
SCVH>OGNSDR,TCPIP*,qAC,GLIDERN4:>153734h v0.2.8.RPI-GPU CPU:0.3 RAM:744.5/968.2MB NTP:3.6ms/+2.0ppm +68.2C 3/3Acfts[1h] Lat:1.6s RF:-8+67.8ppm/+10.33dB/+1.3dB@10km[30998]/+10.4dB@10km[3/5]

Wyświetl plik

@ -8,3 +8,4 @@
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
FLRDF0CBA>OGSPOT,qAS,SPOT:/145808h3317.84S/07021.04W'000/000/A=010085 id0-2120121 SPOTCONNECT GOOD

Wyświetl plik

@ -7,3 +7,5 @@ FLRDD9C70>OGNTRK,OGN2FD00F*,qAS,LZHL:/093021h4848.77N/01708.33E'000/000/A=000518
OGN03AF2A>OGNTRK,qAS,LZHL:/092912h4848.77N/01708.33E'000/000/A=000535 !W53! id0703AF2A +000fpm +0.0rot FL003.15 4.5dB 1e -0.1kHz gps4x5 -11.2dBm
OGN2FD00F>OGNTRK,qAS,LZHL:>092840h h00 v00 11sat/2 165m 1001.9hPa +27.1degC 0% 3.28V 14/-111.5dBm 127/min
FLRDD9C70>OGNTRK,RELAY*,qAS,LZHL:/094124h4848.78N/01708.33E'000/000/A=000397 !W15! id06DD9C70 +099fpm +0.0rot 24.5dB 0e -1.4kHz gps10x15
OGN7402C8>OGNTRK,qAS,OxfBarton:>055357h h02 v01
OGN395F39>OGNTRK,qAS,OxfBarton:>055451h Pilot=Pawel Hard=DIY/STM32