├── MANIFEST.in
├── pizero_gpslog
├── DejaVuSansMono.ttf
├── DejaVuSansCondensed.ttf
├── tests
│ ├── data
│ │ ├── gpsd
│ │ │ ├── bu303-nofix.log
│ │ │ ├── bu303-climbing.log
│ │ │ ├── bu303-moving.log
│ │ │ ├── bu303-stillfix.log
│ │ │ ├── README.rst
│ │ │ ├── bu303-nofix.log.chk
│ │ │ ├── COPYING
│ │ │ ├── bu353s4.log
│ │ │ ├── bu303-climbing.log.chk
│ │ │ ├── bu303-stillfix.log.chk
│ │ │ ├── bu303-moving.log.chk
│ │ │ └── bu353s4.log.chk
│ │ └── runfake.sh
│ ├── __init__.py
│ └── test_version.py
├── __init__.py
├── displays
│ ├── __init__.py
│ ├── base.py
│ ├── dummy.py
│ ├── adafruit4567.py
│ └── epd2in13bc.py
├── extradata
│ ├── __init__.py
│ ├── base.py
│ ├── dummy.py
│ └── gq_gmc500plus.py
├── utils.py
├── version.py
├── fakeled.py
├── screentest.py
├── displaymanager.py
├── installer.py
├── runner.py
├── converter.py
└── gpsd.py
├── setup.cfg
├── .coveragerc
├── tox.ini
├── .travis.yml
├── .gitignore
├── CHANGES.rst
├── setup.py
├── setup_pi.sh
└── README.rst
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include CHANGES.rst
2 | include LICENSE
3 | include README.rst
4 | include pizero_gpslog/*.ttf
5 |
--------------------------------------------------------------------------------
/pizero_gpslog/DejaVuSansMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jantman/pizero-gpslog/HEAD/pizero_gpslog/DejaVuSansMono.ttf
--------------------------------------------------------------------------------
/pizero_gpslog/DejaVuSansCondensed.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jantman/pizero-gpslog/HEAD/pizero_gpslog/DejaVuSansCondensed.ttf
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/gpsd/bu303-nofix.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jantman/pizero-gpslog/HEAD/pizero_gpslog/tests/data/gpsd/bu303-nofix.log
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/gpsd/bu303-climbing.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jantman/pizero-gpslog/HEAD/pizero_gpslog/tests/data/gpsd/bu303-climbing.log
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/gpsd/bu303-moving.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jantman/pizero-gpslog/HEAD/pizero_gpslog/tests/data/gpsd/bu303-moving.log
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/gpsd/bu303-stillfix.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jantman/pizero-gpslog/HEAD/pizero_gpslog/tests/data/gpsd/bu303-stillfix.log
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal=1
3 |
4 | [pycodestyle]
5 | exclude = lib/*,lib64/*,pizero_gpslog/installer.py,pizero_gpslog/displays/epd2in13bc.py
6 | max-line-length = 90
7 |
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/gpsd/README.rst:
--------------------------------------------------------------------------------
1 | pizero_gpslog/tests/data/gpsd
2 | =============================
3 |
4 | The log files in this directory came from the ``test/`` directory of the gpsd source code.
5 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | omit = lib/*
4 | pizero-gpslog/tests/*
5 | setup.py
6 |
7 | [report]
8 | exclude_lines =
9 | # this cant ever be run by py.test, but it just calls one function,
10 | # so ignore it
11 | if __name__ == .__main__.:
12 | if sys.version_info.+
13 | raise NotImplementedError
14 | except ImportError:
15 | .*# nocoverage.*
16 |
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/runfake.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4 | FTYPE=moving
5 |
6 | while [[ $# -gt 0 ]]; do
7 | key="$1"
8 |
9 | case $key in
10 | --nofix)
11 | FTYPE=nofix
12 | shift # past argument
13 | ;;
14 | --stillfix)
15 | FTYPE=stillfix
16 | shift # past argument
17 | ;;
18 | -h|--help)
19 | echo "USAGE: runfake.sh [--nofix|--stillfix]"
20 | exit 1
21 | ;;
22 | esac
23 | done
24 |
25 | gpsfake -S ${DIR}/gpsd/bu303-${FTYPE}.log
26 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py37,py38
3 |
4 | [testenv]
5 | deps =
6 | cov-core
7 | coverage
8 | execnet
9 | pycodestyle
10 | py
11 | pytest
12 | pytest-cache
13 | pytest-cov
14 | pytest-pycodestyle
15 | pytest-flakes
16 | mock
17 | freezegun
18 | pytest-blockage
19 |
20 | passenv=TRAVIS*
21 | setenv =
22 | TOXINIDIR={toxinidir}
23 | TOXDISTDIR={distdir}
24 | sitepackages = False
25 | whitelist_externals = env test
26 |
27 | commands =
28 | python --version
29 | virtualenv --version
30 | pip --version
31 | pip freeze
32 | py.test -rxs -vv --durations=10 --pycodestyle --flakes --blockage --cov-report term-missing --cov-report xml --cov-report html --cov-config {toxinidir}/.coveragerc --cov=pizero_gpslog {posargs} pizero_gpslog
33 |
34 | # always recreate the venv
35 | recreate = True
36 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | cache: pip
3 |
4 | install:
5 | - virtualenv --version
6 | - git config --global user.email "travisci@jasonantman.com"
7 | - git config --global user.name "travisci"
8 | - pip install tox
9 | - pip install codecov
10 | - pip freeze
11 | - virtualenv --version
12 |
13 | script:
14 | - tox -r
15 | after_success:
16 | - codecov
17 |
18 | stages:
19 | - test
20 | - name: deploy
21 | if: tag IS present
22 |
23 | jobs:
24 | include:
25 | - stage: test
26 | python: '3.8'
27 | env: TOXENV=py38
28 | - stage: deploy
29 | python: '3.8'
30 | script: bash build_or_deploy.sh build
31 | after_success: echo after_success
32 | deploy:
33 | provider: script
34 | script: bash build_or_deploy.sh push
35 | skip_cleanup: true
36 | on:
37 | tags: true
38 |
39 | notifications:
40 | email:
41 | on_failure: always
42 |
43 | branches:
44 | except:
45 | - "/^noci-.*$/"
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | bin/
12 | include/
13 | env/
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 | pip-selfcheck.json
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *,cover
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Sphinx documentation
55 | docs/build/
56 |
57 | # PyBuilder
58 | target/
59 |
60 | # virtualenv
61 | bin/
62 | include/
63 |
64 | .idea/
65 | *.out
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/gpsd/bu303-nofix.log.chk:
--------------------------------------------------------------------------------
1 | {"class":"SKY","time":"2002-11-14T14:32:54.280Z"}
2 | $GPRMC,143254,V,18000.0000,N,00000.0000,W,0.0000,0.000,141102,,*13
3 | $GPGSA,A,1,,,,,,,,,,,,,,,,*32
4 | {"class":"TPV","mode":1,"time":"2002-11-14T14:32:54.280Z","ept":0.005}
5 | {"class":"SKY","time":"2002-11-14T14:32:55.280Z","hdop":50.00}
6 | $GPRMC,143255,V,18000.0000,N,00000.0000,W,0.0000,0.000,141102,,*12
7 | $GPGSA,A,1,,,,,,,,,,,,,,,,*32
8 | {"class":"TPV","mode":1,"time":"2002-11-14T14:32:55.280Z","ept":0.005}
9 | {"class":"SKY","time":"2002-11-14T14:32:56.280Z","hdop":50.00}
10 | $GPRMC,143256,V,18000.0000,N,00000.0000,W,0.0000,0.000,141102,,*11
11 | $GPGSA,A,1,,,,,,,,,,,,,,,,*32
12 | {"class":"TPV","mode":1,"time":"2002-11-14T14:32:56.280Z","ept":0.005}
13 | {"class":"SKY","time":"2002-11-14T14:32:57.280Z","hdop":50.00}
14 | $GPRMC,143257,V,18000.0000,N,00000.0000,W,0.0000,0.000,141102,,*10
15 | $GPGSA,A,1,,,,,,,,,,,,,,,,*32
16 | {"class":"TPV","mode":1,"time":"2002-11-14T14:32:57.280Z","ept":0.005}
17 | {"class":"SKY","time":"2002-11-14T14:32:58.280Z","hdop":50.00}
18 | $GPRMC,143258,V,18000.0000,N,00000.0000,W,0.0000,0.000,141102,,*1F
19 | $GPGSA,A,1,,,,,,,,,,,,,,,,*32
20 | {"class":"TPV","mode":1,"time":"2002-11-14T14:32:58.280Z","ept":0.005}
21 | {"class":"SKY","time":"2005-06-09T14:32:59.280Z","hdop":50.00}
22 | $GPRMC,143259,V,18000.0000,N,00000.0000,W,0.0000,0.000,090605,,*13
23 | $GPGSA,A,1,,,,,,,,,,,,,,,,*32
24 | {"class":"TPV","mode":1,"time":"2005-06-09T14:32:59.280Z","ept":0.005}
25 | {"class":"SKY","time":"2005-06-09T14:33:00.280Z","hdop":50.00}
26 | $GPRMC,143300,V,18000.0000,N,00000.0000,W,0.0000,0.000,090605,,*1E
27 | $GPGSA,A,1,,,,,,,,,,,,,,,,*32
28 | {"class":"TPV","mode":1,"time":"2005-06-09T14:33:00.280Z","ept":0.005}
29 |
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/gpsd/COPYING:
--------------------------------------------------------------------------------
1 | COPYRIGHTS
2 |
3 | Compilation copyright is held by the GPSD project. All rights reserved.
4 |
5 | GPSD project copyrights are assigned to the project lead, currently
6 | Eric S. Raymond. Other portions of the GPSD code are Copyright (c)
7 | 1997, 1998, 1999, 2000, 2001, 2002 by Remco Treffkorn, and others
8 | Copyright (c) 2005 by Eric S. Raymond. For other copyrights, see
9 | individual files.
10 |
11 | BSD LICENSE
12 |
13 | Redistribution and use in source and binary forms, with or without
14 | modification, are permitted provided that the following conditions
15 | are met:
16 |
17 | Redistributions of source code must retain the above copyright
18 | notice, this list of conditions and the following disclaimer.
19 |
20 | Redistributions in binary form must reproduce the above copyright
21 | notice, this list of conditions and the following disclaimer in the
22 | documentation and/or other materials provided with the distribution.
23 |
24 | Neither name of the GPSD project nor the names of its contributors
25 | may be used to endorse or promote products derived from this software
26 | without specific prior written permission.
27 |
28 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
32 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
33 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
34 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
35 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
36 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
37 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
38 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 |
--------------------------------------------------------------------------------
/pizero_gpslog/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
--------------------------------------------------------------------------------
/pizero_gpslog/tests/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
--------------------------------------------------------------------------------
/pizero_gpslog/displays/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
--------------------------------------------------------------------------------
/pizero_gpslog/extradata/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
--------------------------------------------------------------------------------
/pizero_gpslog/utils.py:
--------------------------------------------------------------------------------
1 | from threading import Lock
2 | from copy import copy
3 | import logging
4 | from enum import Enum
5 |
6 |
7 | class ThreadSafeValue:
8 |
9 | def __init__(self, initial=''):
10 | self._value = initial
11 | self._lock = Lock()
12 |
13 | def set(self, val):
14 | self._lock.acquire()
15 | try:
16 | self._value = copy(val)
17 | finally:
18 | self._lock.release()
19 |
20 | def get(self):
21 | self._lock.acquire()
22 | try:
23 | result = copy(self._value)
24 | finally:
25 | self._lock.release()
26 | return result
27 |
28 |
29 | class FixType(Enum):
30 |
31 | NO_GPS = 0
32 | NO_FIX = 1
33 | FIX_2D = 2
34 | FIX_3D = 3
35 |
36 |
37 | def set_log_info(log: logging.Logger):
38 | """
39 | set logger level to INFO via :py:func:`~.set_log_level_format`.
40 | """
41 | set_log_level_format(
42 | log, logging.INFO,
43 | '%(asctime)s %(levelname)s:%(name)s:%(message)s'
44 | )
45 |
46 |
47 | def set_log_debug(log: logging.Logger):
48 | """
49 | set logger level to DEBUG, and debug-level output format,
50 | via :py:func:`~.set_log_level_format`.
51 | """
52 | set_log_level_format(
53 | log,
54 | logging.DEBUG,
55 | "%(asctime)s [%(levelname)s %(filename)s:%(lineno)s - "
56 | "%(name)s.%(funcName)s() ] %(message)s"
57 | )
58 |
59 |
60 | def set_log_level_format(log: logging.Logger, level: int, format: str):
61 | """
62 | Set logger level and format.
63 |
64 | :param logger: the logger object to set on
65 | :type logger: logging.Logger
66 | :param level: logging level; see the :py:mod:`logging` constants.
67 | :type level: int
68 | :param format: logging formatter format string
69 | :type format: str
70 | """
71 | formatter = logging.Formatter(fmt=format)
72 | log.handlers[0].setFormatter(formatter)
73 | log.setLevel(level)
74 |
--------------------------------------------------------------------------------
/CHANGES.rst:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | 1.1.0 (2020-09-11)
5 | ------------------
6 |
7 | * Fix ``pizero_gpslog.extradata.gq_gmc500plus:GqGMC500plus`` to handle disconnect/reconnect of USB device.
8 | * Add support for the Adafruit 4567 OLED display.
9 | * Major refactor of display support; instead of taking a list of lines, ``BaseDisplay`` subclasses now take separate values for each piece of data that can be displayed and are free to format them however works best for that particular display.
10 |
11 | 1.0.0 (2020-08-25)
12 | ------------------
13 |
14 | * Update README for current Raspberry Pi OS install instructions
15 | * Add ``setup_pi.sh`` setup script, based on the one at https://github.com/jantman/pi2graphite/blob/master/setup_raspbian.sh
16 | * Add development documentation
17 | * Add support for displays. Begin with the Waveshare 2.13-inch e-Ink Display Hat B; allow users to implement their own display driver classes.
18 | * Implement support for extra data providers.
19 |
20 | 0.1.4 (2020-07-23)
21 | ------------------
22 |
23 | * Merge `PR #5 `__ to have converter ignore corrupt lines. Thanks to `@markus-k `__.
24 | * Use TravisCI for releases; document release process
25 | * PEP8 fixes
26 |
27 | 0.1.3 (2019-12-01)
28 | ------------------
29 |
30 | * Refactor ``pizero-gpslog-convert`` to allow use from other Python programs.
31 |
32 | 0.1.2 (2018-11-03)
33 | ------------------
34 |
35 | * ``pizero-gpslog-convert`` - Handle logs that are missing altitude (``alt``) from TPV
36 | reports by falling back to GST altitude, the previous altitude measurement, or 0.0 (in that order).
37 |
38 | 0.1.1 (2018-04-08)
39 | ------------------
40 |
41 | * Log version at startup
42 | * RPi RTC fix - don't start writing output until we have a fix; use GPS time instead of system time for filename
43 | * Numerous bugfixes in ``converter.py``
44 | * README fixes
45 |
46 | 0.1.0 (2018-03-06)
47 | ------------------
48 |
49 | * Initial Release
50 |
--------------------------------------------------------------------------------
/pizero_gpslog/version.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
38 |
39 | VERSION = '1.1.0'
40 | PROJECT_URL = 'https://github.com/jantman/pizero-gpslog'
41 |
--------------------------------------------------------------------------------
/pizero_gpslog/extradata/base.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
38 | from abc import ABC, abstractmethod
39 | import logging
40 | from threading import Thread
41 |
42 | logger = logging.getLogger(__name__)
43 |
44 |
45 | class BaseExtraDataProvider(ABC, Thread):
46 | """
47 | Base class for all extra data providers.
48 |
49 | ``self._data`` should be a dict with a ``message`` key that has a string
50 | value, and a ``data`` key that has an arbitrary JSON-encodable value.
51 | """
52 |
53 | def __init__(self):
54 | self._data = {}
55 | super().__init__(name='ExtraDataProvider', daemon=True)
56 |
57 | @property
58 | def data(self):
59 | return self._data
60 |
61 | @abstractmethod
62 | def run(self):
63 | raise NotImplementedError()
64 |
--------------------------------------------------------------------------------
/pizero_gpslog/extradata/dummy.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
38 | import logging
39 | from pizero_gpslog.extradata.base import BaseExtraDataProvider
40 | from time import time, sleep
41 | from datetime import datetime
42 |
43 | logger = logging.getLogger(__name__)
44 |
45 |
46 | class DummyData(BaseExtraDataProvider):
47 |
48 | def __init__(self):
49 | super().__init__()
50 |
51 | def run(self):
52 | logger.debug('Running DummyData extra data provider...')
53 | while True:
54 | dt = datetime.now()
55 | hms = dt.strftime('%H:%M:%W')
56 | self._data = {
57 | 'message': f'updated at {hms}',
58 | 'data': {
59 | 'hms': hms,
60 | 'time': time()
61 | }
62 | }
63 | sleep(2)
64 |
--------------------------------------------------------------------------------
/pizero_gpslog/tests/test_version.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
38 | import pizero_gpslog.version as version
39 |
40 | import re
41 |
42 |
43 | class TestVersion(object):
44 |
45 | def test_project_url(self):
46 | expected = 'https://github.com/jantman/pizero-gpslog'
47 | assert version.PROJECT_URL == expected
48 |
49 | def test_is_semver(self):
50 | # see:
51 | # https://github.com/mojombo/semver.org/issues/59#issuecomment-57884619
52 | semver_ptn = re.compile(
53 | r'^'
54 | r'(?P(?:'
55 | r'0|(?:[1-9]\d*)'
56 | r'))'
57 | r'\.'
58 | r'(?P(?:'
59 | r'0|(?:[1-9]\d*)'
60 | r'))'
61 | r'\.'
62 | r'(?P(?:'
63 | r'0|(?:[1-9]\d*)'
64 | r'))'
65 | r'(?:-(?P'
66 | r'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*'
67 | r'))?'
68 | r'(?:\+(?P'
69 | r'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*'
70 | r'))?'
71 | r'$'
72 | )
73 | assert semver_ptn.match(version.VERSION) is not None
74 |
--------------------------------------------------------------------------------
/pizero_gpslog/fakeled.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ################################################################################
36 | """
37 |
38 | import logging
39 |
40 | logger = logging.getLogger(__name__)
41 |
42 |
43 | class FakeLed(object):
44 |
45 | def __init__(self, pin_num, **kwargs):
46 | self.pin_num = pin_num
47 | self._lit = False
48 |
49 | def on(self):
50 | logger.info('%s ON' % self)
51 | self._lit = True
52 |
53 | def off(self):
54 | logger.info('%s OFF' % self)
55 | self._lit = False
56 |
57 | def blink(self, on_time=1, off_time=1, n=None, background=True):
58 | if n is None:
59 | raise RuntimeError('ERROR: method would never return!')
60 | if not background:
61 | raise RuntimeError(
62 | 'ERROR: LED.blink not called from background!'
63 | )
64 | logger.info('%s BLINK on=%s off=%s n=%s background=%s',
65 | self, on_time, off_time, n, background)
66 | self._lit = False
67 |
68 | def toggle(self):
69 | logger.info('%s TOGGLE' % self)
70 | self._lit = not self._lit
71 |
72 | @property
73 | def is_lit(self):
74 | return self._lit
75 |
76 | @property
77 | def pin(self):
78 | return self.pin_num
79 |
80 | def __repr__(self):
81 | return '' % self.pin_num
82 |
--------------------------------------------------------------------------------
/pizero_gpslog/screentest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding:utf-8 -*-
3 | """
4 | The latest version of this package is available at:
5 |
6 |
7 | ##################################################################################
8 | Copyright 2018-2020 Jason Antman
9 |
10 | This file is part of pizero-gpslog, also known as pizero-gpslog.
11 |
12 | pizero-gpslog is free software: you can redistribute it and/or modify
13 | it under the terms of the GNU Affero General Public License as published by
14 | the Free Software Foundation, either version 3 of the License, or
15 | (at your option) any later version.
16 |
17 | pizero-gpslog is distributed in the hope that it will be useful,
18 | but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 | GNU Affero General Public License for more details.
21 |
22 | You should have received a copy of the GNU Affero General Public License
23 | along with pizero-gpslog. If not, see .
24 |
25 | The Copyright and Authors attributions contained herein may not be removed or
26 | otherwise altered, except to add the Author attribution of a contributor to
27 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
28 | ##################################################################################
29 | While not legally required, I sincerely request that anyone who finds
30 | bugs please submit them at or
31 | to me via email, and that you send any contributions or improvements
32 | either as a pull request on GitHub, or to me via email.
33 | ##################################################################################
34 |
35 | AUTHORS:
36 | Jason Antman
37 | ##################################################################################
38 | """
39 |
40 | import os
41 | import logging
42 | import time
43 | from datetime import datetime
44 | from pizero_gpslog.displaymanager import DisplayManager
45 | from pizero_gpslog.utils import set_log_debug
46 |
47 | logging.basicConfig(level=logging.DEBUG)
48 | logger = logging.getLogger()
49 | set_log_debug(logger)
50 |
51 |
52 | def main():
53 | if 'DISPLAY_CLASS' not in os.environ:
54 | logger.warning(
55 | 'DISPLAY_CLASS environment variable not set; using default dummy'
56 | )
57 | os.environ[
58 | 'DISPLAY_CLASS'
59 | ] = 'pizero_gpslog.displays.dummy:DummyDisplay'
60 | modname, clsname = os.environ['DISPLAY_CLASS'].split(':')
61 | dm = DisplayManager(modname, clsname)
62 | dm.start()
63 | for i in range(0, 10):
64 | logger.info('OUTER sleep 5s')
65 | time.sleep(5)
66 | logger.info('OUTER set display')
67 | dm.set_heading(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
68 | dm.set_status('A' + (f'{i}' * 19))
69 | dm.set_lat('B' + (f'{i}' * 19))
70 | dm.set_lon('C' + (f'{i}' * 19))
71 | dm.set_extradata('D' + (f'{i}' * 19))
72 | logger.info('OUTER Finished display-setting loop')
73 |
74 |
75 | if __name__ == "__main__":
76 | main()
77 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
38 | from setuptools import setup, find_packages
39 | from pizero_gpslog.version import VERSION, PROJECT_URL
40 |
41 | with open('README.rst') as file:
42 | long_description = file.read()
43 |
44 | requires = [
45 | 'gpiozero',
46 | 'gpxpy',
47 | 'pint',
48 | 'pillow'
49 | ]
50 |
51 | classifiers = [
52 | 'Development Status :: 5 - Production/Stable',
53 | 'Environment :: No Input/Output (Daemon)',
54 | 'Intended Audience :: End Users/Desktop',
55 | 'Natural Language :: English',
56 | 'Operating System :: POSIX :: Linux',
57 | 'Topic :: Other/Nonlisted Topic',
58 | 'Topic :: System :: Logging',
59 | 'Topic :: Utilities',
60 | 'License :: OSI Approved :: GNU Affero General Public License '
61 | 'v3 or later (AGPLv3+)',
62 | 'Programming Language :: Python :: 3 :: Only',
63 | 'Programming Language :: Python :: 3.7',
64 | 'Programming Language :: Python :: 3.8',
65 | ]
66 |
67 | setup(
68 | name='pizero-gpslog',
69 | version=VERSION,
70 | author='Jason Antman',
71 | author_email='jason@jasonantman.com',
72 | packages=find_packages(),
73 | url=PROJECT_URL,
74 | description='Raspberry Pi Zero gpsd logger with status LEDs.',
75 | long_description=long_description,
76 | install_requires=requires,
77 | entry_points="""
78 | [console_scripts]
79 | pizero-gpslog = pizero_gpslog.runner:main
80 | pizero-gpslog-install = pizero_gpslog.installer:main
81 | pizero-gpslog-convert = pizero_gpslog.converter:main
82 | pizero-gpslog-screentest = pizero_gpslog.screentest:main
83 | """,
84 | keywords="raspberry pi rpi gps log logger gpsd",
85 | classifiers=classifiers,
86 | include_package_data=True,
87 | package_data={
88 | 'pizero_gpslog': ['*.ttf']
89 | }
90 | )
91 |
--------------------------------------------------------------------------------
/pizero_gpslog/displays/base.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
38 | from abc import ABC, abstractmethod
39 | import logging
40 | from PIL import ImageFont
41 | from pkg_resources import resource_filename
42 | from typing import ClassVar, Tuple
43 | from pizero_gpslog.utils import FixType
44 | from datetime import datetime
45 |
46 | logger = logging.getLogger(__name__)
47 |
48 |
49 | class BaseDisplay(ABC):
50 | """
51 | Base class for all displays.
52 | """
53 |
54 | #: width of the display in characters
55 | width_chars: ClassVar[int] = 0
56 |
57 | #: height of the display in lines
58 | height_lines: ClassVar[int] = 0
59 |
60 | #: the minimum number of seconds between refreshes of the display
61 | min_refresh_seconds: ClassVar[int] = 0
62 |
63 | def __init__(self):
64 | """
65 | Initialize the display. This should do everything that's required to
66 | get the display ready to call :py:meth:`~.display`, including clearing
67 | the display if required.
68 | """
69 | pass
70 |
71 | @staticmethod
72 | def font(size_pts: int = 20) -> ImageFont.FreeTypeFont:
73 | f = resource_filename('pizero_gpslog', 'DejaVuSansMono.ttf')
74 | return ImageFont.truetype(f, size_pts)
75 |
76 | @abstractmethod
77 | def update_display(
78 | self, fix_type: FixType, lat: float, lon: float, extradata: str,
79 | fix_precision: Tuple[float, float], dt: datetime, should_clear: bool
80 | ):
81 | """
82 | Write ``self._lines`` to the display.
83 | """
84 | raise NotImplementedError()
85 |
86 | @abstractmethod
87 | def clear(self):
88 | """
89 | Clear the display.
90 | """
91 | raise NotImplementedError()
92 |
93 | @abstractmethod
94 | def __del__(self):
95 | """
96 | Do everything that is needed to cleanup the display.
97 | """
98 | raise NotImplementedError()
99 |
--------------------------------------------------------------------------------
/setup_pi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Raspbian setup script for https://github.com/jantman/pi2graphite
3 |
4 | # check arguments
5 | if [[ "$1" == "-h" || "$1" == "--help" ]]; then
6 | echo "USAGE: setup_pi.sh BASEDEV HOSTNAME AUTHKEYS_PATH [SSID PSK]"
7 | echo "where BASEDEV is the base device, e.g. '/dev/sdX'"
8 | echo "where AUTHKEYS_PATH is the path to an authorized_keys file to copy to the pi user"
9 | exit 0
10 | fi
11 |
12 | if [[ "$#" -ne 3 && "$#" -ne 5 ]]; then
13 | echo "ERROR: wrong number of arguments (see --help)" >&2
14 | exit 1
15 | fi
16 |
17 | # check for root
18 | if [[ "$EUID" != "0" ]]; then
19 | echo "ERROR: this script must be run as root" >&2
20 | exit 1
21 | fi
22 |
23 | # assign to meaningful var names
24 | BASEDEV=$1
25 | HOSTNAME=$2
26 | AUTHKEYS_PATH=$3
27 | SSID=$4
28 | PSK=$5
29 | COUNTRY="US" # WiFi Regulatory Domain
30 |
31 | # check that partitions look right
32 | if [[ ! -e "${BASEDEV}1" ]]; then
33 | echo "ERROR: ${BASEDEV}1 does not exist; wrong BASEDEV or bad partition layout" >&2
34 | exit 1
35 | fi
36 |
37 | if [[ ! -e "${BASEDEV}2" ]]; then
38 | echo "ERROR: ${BASEDEV}2 does not exist; wrong BASEDEV or bad partition layout" >&2
39 | exit 1
40 | fi
41 |
42 | if [[ -e "${BASEDEV}3" ]]; then
43 | echo "ERROR: ${BASEDEV}3 exists; wrong BASEDEV or bad partition layout" >&2
44 | exit 1
45 | fi
46 |
47 | if [[ ! -f $AUTHKEYS_PATH ]]; then
48 | echo "ERROR: AUTHKEYS_PATH (${AUTHKEYS_PATH}) does not exist" >&2
49 | exit 1
50 | fi
51 |
52 | # echo out settings
53 | echo "Starting Raspbian configuration"
54 | echo "Base device: $BASEDEV"
55 | echo "Hostname: $HOSTNAME"
56 | echo "Authorized keys file: $AUTHKEYS_PATH"
57 | if [ -n "$SSID" ]; then
58 | echo "SSID: $SSID"
59 | echo "WiFi Key: $PSK"
60 | echo "WiFi Country: $COUNTRY"
61 | fi
62 |
63 | # make sure it looks right to the user
64 | read -p "Does this look right? [y|N]" response
65 | if [[ "$response" != "y" ]]; then
66 | echo "Ok, exiting." >&2
67 | exit 1
68 | fi
69 |
70 | # create a temporary directory
71 | TMPDIR=$(mktemp -d)
72 |
73 | # error handling
74 | cleanup() {
75 | echo "Syncing disks"
76 | sync
77 | echo "Unmounting $TMPDIR"
78 | umount --recursive $TMPDIR
79 | echo "Removing $TMPDIR"
80 | rm -Rf $TMPDIR
81 | }
82 | trap cleanup 0
83 |
84 | echo "Mounting SD card"
85 | mount "${BASEDEV}2" $TMPDIR
86 | mount "${BASEDEV}1" "${TMPDIR}/boot"
87 |
88 | echo "Done mounting SD card; chroot'ing"
89 |
90 | echo "Touching boot/ssh"
91 | touch "${TMPDIR}/boot/ssh"
92 |
93 | PI_UID=$(stat --printf='%u' "${TMPDIR}/home/pi")
94 | PI_GID=$(stat --printf='%g' "${TMPDIR}/home/pi")
95 | echo "Found pi user; UID=${PI_UID} GID=${PI_GID}"
96 |
97 | if [[ ! -e "${TMPDIR}/home/pi/.ssh" ]]; then
98 | echo "Creating pi user .ssh directory"
99 | install -d -m 0700 -o $PI_UID -g $PI_GID "${TMPDIR}/home/pi/.ssh"
100 | fi
101 |
102 | AKPATH="${TMPDIR}/home/pi/.ssh/authorized_keys"
103 | echo "Copying authorized keys file (${AUTHKEYS_PATH}) to $AKPATH"
104 | install -m 0644 -o $PI_UID -g $PI_GID $AUTHKEYS_PATH $AKPATH
105 |
106 | echo "Setting hostname"
107 | echo $HOSTNAME > "${TMPDIR}/etc/hostname"
108 | sed -i "s/raspberrypi/${HOSTNAME}/g" "${TMPDIR}/etc/hosts"
109 |
110 | if [ -n "$SSID" ]; then
111 | echo "Setting up WiFi configuration..."
112 | if ! grep "country=${COUNTRY}" "${TMPDIR}/etc/wpa_supplicant/wpa_supplicant.conf" &>/dev/null; then
113 | echo "Setting country code"
114 | sed -i "s/country=.*/country=${COUNTRY}/" "${TMPDIR}/etc/wpa_supplicant/wpa_supplicant.conf"
115 | fi
116 | netconf=$(cat </dev/null; then
124 | echo "Adding network configuration"
125 | echo "$netconf" >> "${TMPDIR}/etc/wpa_supplicant/wpa_supplicant.conf"
126 | fi
127 | fi
128 |
--------------------------------------------------------------------------------
/pizero_gpslog/displays/dummy.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
38 | import logging
39 | from pizero_gpslog.displays.base import BaseDisplay
40 | from pizero_gpslog.utils import FixType
41 | from typing import ClassVar, Tuple
42 | from datetime import datetime
43 | import time
44 | import os
45 |
46 | logger = logging.getLogger(__name__)
47 |
48 |
49 | class DummyDisplay(BaseDisplay):
50 |
51 | #: width of the display in characters
52 | width_chars: ClassVar[int] = 21
53 |
54 | #: height of the display in lines
55 | height_lines: ClassVar[int] = 5
56 |
57 | #: the minimum number of seconds between refreshes of the display
58 | min_refresh_seconds: ClassVar[int] = 15
59 |
60 | def __init__(self):
61 | super().__init__()
62 | self.sleep_time: int = int(os.environ.get('DUMMY_SLEEP_TIME', '2'))
63 | logger.debug(
64 | 'Initialize DummyDisplay; sleep time (%d sec) set by '
65 | 'DUMMY_SLEEP_TIME environment variable.'
66 | )
67 |
68 | def update_display(
69 | self, fix_type: FixType, lat: float, lon: float, extradata: str,
70 | fix_precision: Tuple[float, float], dt: datetime, should_clear: bool
71 | ):
72 | if should_clear:
73 | self.clear()
74 | lines = [dt.strftime('%H:%M:%S UTC')]
75 | if fix_type == FixType.NO_GPS:
76 | lines.extend(['No GPS yet', '', ''])
77 | elif fix_type == FixType.NO_FIX:
78 | lines.extend(['No Fix yet', '', ''])
79 | else:
80 | ft = '??'
81 | if fix_type == FixType.FIX_2D:
82 | ft = '2D'
83 | elif fix_type == FixType.FIX_3D:
84 | ft = '3D'
85 | lines.append(f'{ft} {fix_precision[0]:.8},{fix_precision[1]:.8}')
86 | lines.append(f'Lat: {lat:.15}')
87 | lines.append(f'Lon: {lon:.15}')
88 | lines.append(extradata)
89 | self._write_lines(lines)
90 |
91 | def _write_lines(self, lines):
92 | fmt: str = 'DUMMYDISPLAY>|%-' + '%ds|' % self.width_chars
93 | for line in lines:
94 | logger.warning(fmt, line)
95 | logger.debug('Dummy display sleeping %d seconds...', self.sleep_time)
96 | time.sleep(self.sleep_time)
97 |
98 | def clear(self):
99 | logger.warning('------ DUMMYDISPLAY CLEAR -------')
100 |
101 | def __del__(self):
102 | logger.warning('DUMMYDISPLAY __del__')
103 |
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/gpsd/bu353s4.log:
--------------------------------------------------------------------------------
1 | # Name: GlobalSat BU-353-S4
2 | # Chipset = SiRF-IV
3 | # Firmware = ?-GSD4e_4.0.4-P1_RPATCH.07-GS008-F-GPS-4R-1201128 01/12/2012 012
4 | # Date = 2014-12-31
5 | # Submitter = Paul Beard
6 | # Location = Hamilton, Waikato, 37.8S 175.3E
7 | $GPGGA,030719.000,3747.0873,S,17518.8938,E,1,08,1.1,59.9,M,23.7,M,,0000*7D
8 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
9 | $GPRMC,030719.000,A,3747.0873,S,17518.8938,E,0.69,181.39,311214,,,A*7D
10 | $GPGGA,030720.000,3747.0875,S,17518.8938,E,1,08,1.1,59.8,M,23.7,M,,0000*70
11 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
12 | $GPRMC,030720.000,A,3747.0875,S,17518.8938,E,1.15,181.39,311214,,,A*7B
13 | $GPGGA,030721.000,3747.0876,S,17518.8934,E,1,08,1.1,60.1,M,23.7,M,,0000*7D
14 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
15 | $GPGSV,3,1,12,17,72,207,32,28,59,095,31,06,35,347,25,01,29,132,23*7B
16 | $GPGSV,3,2,12,30,23,011,29,20,17,069,20,26,09,293,23,13,07,304,10*7C
17 | $GPGSV,3,3,12,11,12,132,04,08,38,008,,02,35,348,,03,14,091,*73
18 | $GPRMC,030721.000,A,3747.0876,S,17518.8934,E,0.89,181.39,311214,,,A*71
19 | $GPGGA,030722.000,3747.0877,S,17518.8933,E,1,08,1.1,60.1,M,23.7,M,,0000*78
20 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
21 | $GPRMC,030722.000,A,3747.0877,S,17518.8933,E,0.12,181.39,311214,,,A*76
22 | $GPGGA,030723.000,3747.0878,S,17518.8935,E,1,08,1.1,60.0,M,23.7,M,,0000*71
23 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
24 | $GPRMC,030723.000,A,3747.0878,S,17518.8935,E,0.66,181.39,311214,,,A*7D
25 | $GPGGA,030724.000,3747.0878,S,17518.8936,E,1,08,1.1,60.1,M,23.7,M,,0000*74
26 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
27 | $GPRMC,030724.000,A,3747.0878,S,17518.8936,E,0.32,181.39,311214,,,A*78
28 | $GPGGA,030725.000,3747.0878,S,17518.8936,E,1,08,1.1,60.2,M,23.7,M,,0000*76
29 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
30 | $GPRMC,030725.000,A,3747.0878,S,17518.8936,E,0.00,181.39,311214,,,A*78
31 | $GPGGA,030726.000,3747.0877,S,17518.8936,E,1,08,1.1,60.6,M,23.7,M,,0000*7E
32 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
33 | $GPGSV,3,1,12,17,72,207,32,28,59,095,30,06,35,347,25,01,29,132,23*7A
34 | $GPGSV,3,2,12,30,23,011,29,20,17,069,20,26,09,293,23,13,07,304,10*7C
35 | $GPGSV,3,3,12,11,12,132,04,08,38,008,,02,35,348,,03,14,091,*73
36 | $GPRMC,030726.000,A,3747.0877,S,17518.8936,E,0.59,181.39,311214,,,A*78
37 | $GPGGA,030727.000,3747.0877,S,17518.8938,E,1,08,1.1,60.8,M,23.7,M,,0000*7F
38 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
39 | $GPRMC,030727.000,A,3747.0877,S,17518.8938,E,0.29,181.39,311214,,,A*70
40 | $GPGGA,030728.000,3747.0878,S,17518.8940,E,1,08,1.1,60.5,M,23.7,M,,0000*7D
41 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
42 | $GPRMC,030728.000,A,3747.0878,S,17518.8940,E,0.52,181.39,311214,,,A*73
43 | $GPGGA,030729.000,3747.0879,S,17518.8942,E,1,08,1.1,60.7,M,23.7,M,,0000*7D
44 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
45 | $GPRMC,030729.000,A,3747.0879,S,17518.8942,E,0.48,181.39,311214,,,A*7A
46 | $GPGGA,030730.000,3747.0878,S,17518.8942,E,1,07,1.1,60.5,M,23.7,M,,0000*79
47 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
48 | $GPRMC,030730.000,A,3747.0878,S,17518.8942,E,0.72,181.39,311214,,,A*7A
49 | $GPGGA,030731.000,3747.0877,S,17518.8941,E,1,07,1.1,60.5,M,23.7,M,,0000*74
50 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
51 | $GPGSV,3,1,12,17,71,207,32,28,59,095,30,06,36,347,25,01,29,132,23*7A
52 | $GPGSV,3,2,12,30,23,011,29,20,17,070,19,26,08,293,24,13,07,305,07*7F
53 | $GPGSV,3,3,12,08,38,008,,02,35,348,,03,15,091,,24,12,219,*7A
54 | $GPRMC,030731.000,A,3747.0877,S,17518.8941,E,0.47,181.39,311214,,,A*71
55 | $GPGGA,030732.000,3747.0876,S,17518.8940,E,1,07,1.1,60.4,M,23.7,M,,0000*76
56 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
57 | $GPRMC,030732.000,A,3747.0876,S,17518.8940,E,0.43,181.39,311214,,,A*76
58 | $GPGGA,030733.000,3747.0876,S,17518.8941,E,1,07,1.1,60.2,M,23.7,M,,0000*70
59 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
60 | $GPRMC,030733.000,A,3747.0876,S,17518.8941,E,0.23,181.39,311214,,,A*70
61 | $GPGGA,030734.000,3747.0876,S,17518.8941,E,1,07,1.1,60.0,M,23.7,M,,0000*75
62 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
63 | $GPRMC,030734.000,A,3747.0876,S,17518.8941,E,0.51,181.39,311214,,,A*72
64 | $GPGGA,030735.000,3747.0876,S,17518.8941,E,1,07,1.1,60.0,M,23.7,M,,0000*74
65 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
66 | $GPRMC,030735.000,A,3747.0876,S,17518.8941,E,0.00,181.39,311214,,,A*77
67 | $GPGGA,030736.000,3747.0876,S,17518.8941,E,1,07,1.1,60.0,M,23.7,M,,0000*77
68 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
69 | $GPGSV,3,1,12,17,71,207,32,28,59,095,30,06,36,347,25,01,29,132,23*7A
70 | $GPGSV,3,2,12,30,23,011,29,20,17,070,19,26,08,293,24,13,07,305,04*7C
71 | $GPGSV,3,3,12,08,38,008,,02,35,348,,03,15,091,,24,12,219,*7A
72 | $GPRMC,030736.000,A,3747.0876,S,17518.8941,E,0.00,181.39,311214,,,A*74
73 | $GPGGA,030737.000,3747.0877,S,17518.8942,E,1,07,1.1,60.0,M,23.7,M,,0000*74
74 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
75 | $GPRMC,030737.000,A,3747.0877,S,17518.8942,E,0.25,181.39,311214,,,A*70
76 | $GPGGA,030738.000,3747.0877,S,17518.8942,E,1,07,1.1,60.0,M,23.7,M,,0000*7B
77 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
78 | $GPRMC,030738.000,A,3747.0877,S,17518.8942,E,0.00,181.39,311214,,,A*78
79 | $GPGGA,030739.000,3747.0877,S,17518.8942,E,1,07,1.1,60.0,M,23.7,M,,0000*7A
80 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
81 | $GPRMC,030739.000,A,3747.0877,S,17518.8942,E,0.00,181.39,311214,,,A*79
82 | $GPGGA,030740.000,3747.0877,S,17518.8942,E,1,07,1.1,60.2,M,23.7,M,,0000*76
83 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
84 | $GPRMC,030740.000,A,3747.0877,S,17518.8942,E,0.26,181.39,311214,,,A*73
85 | $GPGGA,030741.000,3747.0877,S,17518.8941,E,1,07,1.1,60.2,M,23.7,M,,0000*74
86 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
87 | $GPGSV,3,1,12,17,71,207,31,28,59,095,30,06,36,347,26,01,29,132,23*7A
88 | $GPGSV,3,2,12,30,23,011,29,20,17,070,18,26,08,293,24,13,07,305,04*7D
89 | $GPGSV,3,3,12,08,38,008,,02,35,348,,03,15,091,,24,12,219,*7A
90 | $GPRMC,030741.000,A,3747.0877,S,17518.8941,E,0.00,181.39,311214,,,A*75
91 | $GPGGA,030742.000,3747.0877,S,17518.8941,E,1,07,1.1,60.2,M,23.7,M,,0000*77
92 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
93 | $GPRMC,030742.000,A,3747.0877,S,17518.8941,E,0.00,181.39,311214,,,A*76
94 | $GPGGA,030743.000,3747.0877,S,17518.8941,E,1,07,1.1,60.2,M,23.7,M,,0000*76
95 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
96 | $GPRMC,030743.000,A,3747.0877,S,17518.8941,E,0.00,181.39,311214,,,A*77
97 |
--------------------------------------------------------------------------------
/pizero_gpslog/displays/adafruit4567.py:
--------------------------------------------------------------------------------
1 | """
2 | Modified from:
3 | https://github.com/waveshare/e-Paper/blob/
4 | 717cbb8d9215e58f9f3cdde45ee329f516504afe/RaspberryPi%26JetsonNano/python/
5 | lib/waveshare_epd/epd2in13bc.py
6 |
7 | Display driver class for Waveshare e-Paper Display HAT 2.13 inch (B)
8 |
9 | https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT_(B)
10 | https://www.amazon.com/gp/product/B075FR81WL/
11 |
12 | The latest version of this package is available at:
13 |
14 |
15 | ##################################################################################
16 | Copyright 2018-2020 Jason Antman
17 |
18 | This file is part of pizero-gpslog, also known as pizero-gpslog.
19 |
20 | pizero-gpslog is free software: you can redistribute it and/or modify
21 | it under the terms of the GNU Affero General Public License as published by
22 | the Free Software Foundation, either version 3 of the License, or
23 | (at your option) any later version.
24 |
25 | pizero-gpslog is distributed in the hope that it will be useful,
26 | but WITHOUT ANY WARRANTY; without even the implied warranty of
27 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 | GNU Affero General Public License for more details.
29 |
30 | You should have received a copy of the GNU Affero General Public License
31 | along with pizero-gpslog. If not, see .
32 |
33 | The Copyright and Authors attributions contained herein may not be removed or
34 | otherwise altered, except to add the Author attribution of a contributor to
35 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
36 | ##################################################################################
37 | While not legally required, I sincerely request that anyone who finds
38 | bugs please submit them at or
39 | to me via email, and that you send any contributions or improvements
40 | either as a pull request on GitHub, or to me via email.
41 | ##################################################################################
42 |
43 | AUTHORS:
44 | Jason Antman
45 | ##################################################################################
46 | """
47 |
48 | import logging
49 | from typing import ClassVar, Tuple
50 | from board import SCL, SDA, D4
51 | import busio
52 | import digitalio
53 | import adafruit_ssd1305
54 | from pizero_gpslog.displays.base import BaseDisplay
55 | from pizero_gpslog.utils import FixType
56 | from PIL import Image, ImageDraw
57 | from datetime import datetime
58 |
59 | logger = logging.getLogger(__name__)
60 |
61 |
62 | class Adafruit4567(BaseDisplay):
63 |
64 | #: width of the display in characters
65 | width_chars: ClassVar[int] = 20
66 |
67 | #: height of the display in lines
68 | height_lines: ClassVar[int] = 4
69 |
70 | #: the minimum number of seconds between refreshes of the display
71 | min_refresh_seconds: ClassVar[int] = 0.1
72 |
73 | def __init__(self):
74 | super().__init__()
75 | self._oled_reset = digitalio.DigitalInOut(D4)
76 | self._i2c = busio.I2C(SCL, SDA)
77 | # Create the SSD1305 OLED class.
78 | # The first two parameters are the pixel width and pixel height.
79 | # Change these to the right size for your display!
80 | self._disp = adafruit_ssd1305.SSD1305_I2C(
81 | 128, 32, self._i2c, reset=self._oled_reset
82 | )
83 | self.clear()
84 | # Create blank image for drawing.
85 | # Make sure to create image with mode '1' for 1-bit color.
86 | self._width = self._disp.width
87 | self._height = self._disp.height
88 | self._image = Image.new("1", (self._width, self._height))
89 | # Get drawing object to draw on image.
90 | self._draw = ImageDraw.Draw(self._image)
91 | # Draw a black filled box to clear the image.
92 | self._draw.rectangle(
93 | (0, 0, self._width, self._height), outline=0, fill=0
94 | )
95 | self._top = -2
96 | self._font = BaseDisplay.font(8)
97 |
98 | def update_display(
99 | self, fix_type: FixType, lat: float, lon: float, extradata: str,
100 | fix_precision: Tuple[float, float], dt: datetime, should_clear: bool
101 | ):
102 | if should_clear:
103 | self.clear()
104 | dts = dt.strftime('%H:%M:%S Z')
105 | if fix_type == FixType.NO_GPS:
106 | return self._write_lines([
107 | dts,
108 | 'No GPS yet',
109 | '',
110 | extradata
111 | ])
112 | if fix_type == FixType.NO_FIX:
113 | return self._write_lines([
114 | dts,
115 | 'No Fix yet',
116 | '',
117 | extradata
118 | ])
119 | ft = '??'
120 | if fix_type == FixType.FIX_2D:
121 | ft = '2D'
122 | elif fix_type == FixType.FIX_3D:
123 | ft = '3D'
124 | if extradata is not None and extradata.strip() != '':
125 | return self._write_lines([
126 | dts + f' | {ft} fix',
127 | f'Lat: {lat:.15}',
128 | f'Lon: {lon:.15}',
129 | extradata
130 | ])
131 | # else we don't have extradata, so we have an extra line...
132 | return self._write_lines([
133 | dts,
134 | f'{ft} fix: {fix_precision[0]:.7},{fix_precision[1]:.7}',
135 | f'Lat: {lat:.15}',
136 | f'Lon: {lon:.15}'
137 | ])
138 |
139 | def _write_lines(self, lines):
140 | logging.info('Begin update display')
141 | self._draw.rectangle(
142 | (0, 0, self._width, self._height), outline=0, fill=0
143 | )
144 | for idx, content in enumerate(lines):
145 | coords = (0, self._top + (idx * 8))
146 | self._draw.text(
147 | coords, content, font=self._font, fill=255
148 | )
149 | # Display image.
150 | self._disp.image(self._image)
151 | self._disp.show()
152 | logging.info('End update display')
153 |
154 | def clear(self):
155 | self._disp.fill(0)
156 | self._disp.show()
157 |
158 | def __del__(self):
159 | self.clear()
160 |
--------------------------------------------------------------------------------
/pizero_gpslog/displaymanager.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
38 | import os
39 | import logging
40 | from importlib import import_module
41 | import time
42 | from threading import Thread
43 | from typing import Optional, Tuple
44 | from datetime import datetime, timezone
45 | from pizero_gpslog.displays.base import BaseDisplay
46 | from pizero_gpslog.utils import ThreadSafeValue, FixType
47 |
48 | logger = logging.getLogger(__name__)
49 |
50 |
51 | class DisplayWriterThread(Thread):
52 |
53 | def __init__(
54 | self, driver_cls: BaseDisplay.__class__, fix_type: ThreadSafeValue,
55 | lat: ThreadSafeValue, lon: ThreadSafeValue,
56 | extradata: ThreadSafeValue, fix_precision: ThreadSafeValue,
57 | should_clear: ThreadSafeValue, refresh_sec: int = 0
58 | ):
59 | super().__init__(name='DisplayWriter', daemon=True)
60 | self._fix_type: ThreadSafeValue = fix_type
61 | self._lat: ThreadSafeValue = lat
62 | self._lon: ThreadSafeValue = lon
63 | self._extradata: ThreadSafeValue = extradata
64 | self._fix_precision: ThreadSafeValue = fix_precision
65 | self._should_clear: ThreadSafeValue = should_clear
66 | self._driver_cls: BaseDisplay.__class__ = driver_cls
67 | self._refresh_sec = refresh_sec
68 | logger.info(
69 | 'Initialize DisplayWriterThread; driver_class=%s refresh_sec=%s',
70 | driver_cls, refresh_sec
71 | )
72 |
73 | def run(self):
74 | logger.debug('Initialize display driver class')
75 | driver: BaseDisplay = self._driver_cls()
76 | if (
77 | self._refresh_sec != 0 and
78 | driver.min_refresh_seconds > self._refresh_sec
79 | ):
80 | logger.debug(
81 | 'Set refresh_sec to %s based on driver\'s '
82 | 'min_refresh_seconds=%s', self._refresh_sec,
83 | driver.min_refresh_seconds
84 | )
85 | self._refresh_sec = driver.min_refresh_seconds
86 | logger.info('Refresh display every %d seconds', self._refresh_sec)
87 | while True:
88 | start = time.time()
89 | self.iteration(driver)
90 | duration = time.time() - start
91 | if duration < self._refresh_sec:
92 | t = self._refresh_sec - duration
93 | logger.debug('Sleep %s sec before next refresh', t)
94 | time.sleep(t)
95 |
96 | def iteration(self, driver: BaseDisplay):
97 | driver.update_display(
98 | fix_type=self._fix_type.get(),
99 | fix_precision=self._fix_precision.get(),
100 | lat=self._lat.get(), lon=self._lon.get(),
101 | extradata=self._extradata.get(),
102 | dt=datetime.now(timezone.utc),
103 | should_clear=self._should_clear.get()
104 | )
105 | self._should_clear.set(False)
106 |
107 |
108 | class DisplayManager:
109 |
110 | def __init__(self, modname: str, clsname: str):
111 | self._fix_type: ThreadSafeValue = ThreadSafeValue(FixType.NO_GPS)
112 | self._fix_precision: ThreadSafeValue = ThreadSafeValue((0.0, 0.0))
113 | self._lat: ThreadSafeValue = ThreadSafeValue()
114 | self._lon: ThreadSafeValue = ThreadSafeValue()
115 | self._extradata: ThreadSafeValue = ThreadSafeValue()
116 | self._should_clear: ThreadSafeValue = ThreadSafeValue(False)
117 | self._writer_thread: Optional[DisplayWriterThread] = None
118 | logger.debug('Import %s:%s', modname, clsname)
119 | mod = import_module(modname)
120 | self._driver_cls: BaseDisplay.__class__ = getattr(mod, clsname)
121 | self.clear()
122 |
123 | def start(self):
124 | refresh_sec = int(os.environ.get('DISPLAY_REFRESH_SEC', '0'))
125 | self._writer_thread = DisplayWriterThread(
126 | self._driver_cls, self._fix_type, self._lat, self._lon,
127 | self._extradata, self._fix_precision, self._should_clear,
128 | refresh_sec=refresh_sec
129 | )
130 | self._writer_thread.start()
131 |
132 | def set_fix_type(self, gps_status: FixType):
133 | self._fix_type.set(gps_status)
134 |
135 | def set_fix_precision(self, precision: Tuple[float, float]):
136 | self._fix_precision.set(precision)
137 |
138 | def set_lat(self, lat: float):
139 | self._lat.set(lat)
140 |
141 | def set_lon(self, lon: float):
142 | self._lon.set(lon)
143 |
144 | def set_extradata(self, s: str):
145 | self._extradata.set(s)
146 |
147 | def clear(self):
148 | self._should_clear.set(True)
149 |
--------------------------------------------------------------------------------
/pizero_gpslog/extradata/gq_gmc500plus.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 |
37 | Note: for dependencies, this requires:
38 |
39 | pyudev==0.22.0
40 |
41 | """
42 |
43 | import logging
44 | import os
45 | from glob import iglob
46 | from pyudev import Context, Devices
47 | from time import sleep, time
48 | from gmc import GMC
49 | from pizero_gpslog.extradata.base import BaseExtraDataProvider
50 | if not hasattr(GMC, 'get_config'):
51 | raise RuntimeError(
52 | 'ERROR: gmc must be installed from jantman\'s fork on the '
53 | 'jantman-fixes-config branch; pip install '
54 | 'git+https://gitlab.com/jantman/gmc.git@jantman-fixes-config'
55 | )
56 |
57 | logger = logging.getLogger(__name__)
58 |
59 |
60 | class GqGMC500plus(BaseExtraDataProvider):
61 |
62 | _gmc_vendor_model_revision = [
63 | ('1a86', '7523', '0263')
64 | ]
65 |
66 | def __init__(self, devname=None):
67 | super().__init__()
68 | self._original_devname = devname
69 | self._gmc = None
70 | self._data = self._default_response()
71 | self._sleep_time = int(os.environ.get('GMC_SLEEP_SEC', '5'))
72 | logger.info(
73 | 'Sleeping %d seconds between GMC polls; override by setting '
74 | 'GMC_SLEEP_SEC environment variable as an int', self._sleep_time
75 | )
76 | self._init_gmc()
77 |
78 | def _default_response(self):
79 | return {
80 | 'message': '',
81 | 'data': {
82 | 'time': time(),
83 | 'cps': None,
84 | 'cpsl': None,
85 | 'cpsh': None,
86 | 'cpm': None,
87 | 'cpml': None,
88 | 'cpmh': None,
89 | 'maxcps': None,
90 | 'calibration': None
91 | }
92 | }
93 |
94 | def _try_init(self):
95 | if self._original_devname is None:
96 | devname = self._find_usb_device()
97 | else:
98 | devname = self._original_devname
99 | if devname is None:
100 | logger.critical(
101 | 'ERROR: No devname given, and could not determine GMC-500+ '
102 | 'device name using pyudev.'
103 | )
104 | self._devname = devname
105 | return
106 | self._devname = devname
107 | logger.info('Using device: %s', devname)
108 | self._gmc = None
109 | logger.debug('Connecting to GMC...')
110 | self._gmc = GMC(config_update={'DEFAULT_PORT': self._devname})
111 | logger.debug('Connected.')
112 | self._config = self._gmc.get_config()
113 | logger.info(
114 | 'GMC current time: %s; version: %s; serial: %s; voltage: %s; '
115 | 'config: %s', self._gmc.get_date_time(), self._gmc.version(),
116 | self._gmc.serial(), self._gmc.voltage(), self._config
117 | )
118 | calib_fields = [
119 | 'CalibCPM_0', 'CalibuSv_0', 'CalibCPM_1', 'CalibuSv_1',
120 | 'CalibCPM_2', 'CalibuSv_2'
121 | ]
122 | self._calibration = {
123 | x: self._config[x] for x in calib_fields
124 | }
125 |
126 | def _init_gmc(self):
127 | self._gmc = None
128 | self._data = self._default_response()
129 | try:
130 | self._try_init()
131 | return
132 | except Exception as ex:
133 | logger.critical(
134 | 'Error initializing GMC; try again in 10s', ex
135 | )
136 | logger.debug('GMC init error: %s', ex, exc_info=True)
137 | sleep(10)
138 |
139 | def run(self):
140 | logger.debug('Running extra data provider...')
141 | while True:
142 | try:
143 | cps = self._gmc.cps(numeric=True)
144 | cpsl = self._gmc.cpsl(numeric=True)
145 | cpsh = self._gmc.cpsh(numeric=True)
146 | cpm = self._gmc.cpm(numeric=True)
147 | cpml = self._gmc.cpml(numeric=True)
148 | cpmh = self._gmc.cpmh(numeric=True)
149 | maxcps = self._gmc.max_cps(numeric=True)
150 | logger.debug('End querying GMC')
151 | self._data = {
152 | 'message': f'{cps} CPS | {cpm} CPM',
153 | 'data': {
154 | 'time': time(),
155 | 'cps': cps,
156 | 'cpsl': cpsl,
157 | 'cpsh': cpsh,
158 | 'cpm': cpm,
159 | 'cpml': cpml,
160 | 'cpmh': cpmh,
161 | 'maxcps': maxcps,
162 | 'calibration': self._calibration
163 | }
164 | }
165 | except Exception as ex:
166 | logger.error(
167 | 'Error querying GMC; re-init. Error: %s', ex, exc_info=True
168 | )
169 | self._data = self._default_response()
170 | self._init_gmc()
171 | sleep(self._sleep_time)
172 |
173 | def _find_usb_device(self):
174 | logger.debug('Using pyudev to find GMC tty device')
175 | context = Context()
176 | for devname in iglob('/dev/ttyUSB*'):
177 | device = Devices.from_device_file(context, devname)
178 | if device.properties['ID_BUS'] != 'usb':
179 | continue
180 | k = (
181 | device.properties['ID_VENDOR_ID'],
182 | device.properties['ID_MODEL_ID'],
183 | device.properties['ID_REVISION']
184 | )
185 | if k in self._gmc_vendor_model_revision:
186 | logger.debug('Found GMC-500+ at: %s', devname)
187 | return devname
188 | return None
189 |
190 | def __del__(self):
191 | logger.info('Closing GMC device')
192 | self._gmc.close_device()
193 |
--------------------------------------------------------------------------------
/pizero_gpslog/installer.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
38 | import os
39 | import sys
40 | import logging
41 | import argparse
42 | from distutils.spawn import find_executable
43 | from textwrap import dedent
44 | from subprocess import run
45 | from pizero_gpslog.utils import set_log_debug
46 |
47 | FORMAT = "[%(asctime)s %(levelname)s] %(message)s"
48 | logging.basicConfig(level=logging.INFO, format=FORMAT)
49 | logger = logging.getLogger()
50 |
51 |
52 | class Installer(object):
53 |
54 | def __init__(self, args):
55 | self._systemctl = find_executable('systemctl')
56 | if self._systemctl is None:
57 | raise SystemExit(
58 | 'ERROR: Cannot find "systemctl" executable. This installer '
59 | 'can only be used on systems with systemd running.'
60 | )
61 | self.args = args
62 |
63 | def run(self):
64 | if self.args.dry_run:
65 | print(self.unit_file)
66 | return
67 | unitpath = '/etc/systemd/system/pizero-gpslog.service'
68 | if os.path.exists(unitpath):
69 | logger.warning(
70 | 'Unit file at %s already exists; replacing', unitpath
71 | )
72 | logger.info('Writing systemd unit file:\n%s', self.unit_file)
73 | with open(unitpath, 'w') as fh:
74 | fh.write(self.unit_file)
75 | logger.info('systemd unit file written to: %s', unitpath)
76 | logger.info('Running "%s daemon-reload"' % self._systemctl)
77 | run([self._systemctl, 'daemon-reload'], check=True)
78 | logger.info('Running "%s enable pizero-gpslog"' % self._systemctl)
79 | run([self._systemctl, 'enable', 'pizero-gpslog'], check=True)
80 | logger.info('Installation complete. Service enabled.')
81 |
82 | @property
83 | def unit_file(self):
84 | return dedent("""
85 | [Unit]
86 | Description=pizero-gpslog service
87 | Documentation=https://github.com/jantman/pizero-gpslog
88 | Requires=gpsd.service
89 | After=gpsd.service
90 | AssertArchitecture=arm
91 | [Service]
92 | Type=simple
93 | ExecStart={python} {fpath}
94 | WorkingDirectory={dirpath}
95 | User={user}
96 | Group={group}
97 | Environment=OUT_DIR={dirpath} FLUSH_FILE={flush} GPS_INTERVAL_SEC={intvl} LOG_LEVEL={log} {red} {green}
98 | RestartSec=10
99 | Restart=always
100 | [Install]
101 | WantedBy=default.target
102 | """.format(
103 | fpath=find_executable('pizero-gpslog'),
104 | python=sys.executable,
105 | user=self.args.user,
106 | group=self.args.group,
107 | dirpath=self.args.OUT_DIR,
108 | flush=('false' if self.args.FLUSH_FILE else 'true'),
109 | intvl=self.args.GPS_INTERVAL_SEC,
110 | log=self.args.LOG_LEVEL,
111 | red=(
112 | 'LED_PIN_RED=%s' % self.args.LED_PIN_RED
113 | if self.args.LED_PIN_RED is not None else ''
114 | ),
115 | green=(
116 | 'LED_PIN_GREEN=%s' % self.args.LED_PIN_GREEN
117 | if self.args.LED_PIN_GREEN is not None else ''
118 | )
119 | )).strip()
120 |
121 |
122 | def parse_args(argv):
123 | """parse arguments/options"""
124 | p = argparse.ArgumentParser(description='pizero-gpslog installer - sets up '
125 | 'systemd service')
126 | p.add_argument('-D', '--dry-run', dest='dry_run', action='store_true',
127 | default=False,
128 | help='Print generated systemd unit file to STDOUT and exit')
129 | p.add_argument('-v', '--verbose', dest='verbose', action='store_true',
130 | default=False,
131 | help='enable debug-level output.')
132 | p.add_argument(
133 | '-l', '--log-level', dest='LOG_LEVEL', action='store', type=str,
134 | choices=['WARNING', 'INFO', 'DEBUG'], default='WARNING',
135 | help='Log level to run daemon with; defaults to WARNING'
136 | )
137 | p.add_argument(
138 | '-r', '--red-pin', dest='LED_PIN_RED', action='store', type=int,
139 | default=None,
140 | help='GPIO Pin number for Red (primary) LED; omit to use fake '
141 | '(log to STDOUT) LEDs. Defaults to None (omitted).'
142 | )
143 | p.add_argument(
144 | '-g', '--green-pin', dest='LED_PIN_GREEN', action='store', type=int,
145 | default=None,
146 | help='GPIO Pin number for Green (secondary) LED; omit to use fake '
147 | '(log to STDOUT) LEDs. Defaults to None (omitted).'
148 | )
149 | p.add_argument(
150 | '-i', '--interval', dest='GPS_INTERVAL_SEC', action='store', type=int,
151 | default=5,
152 | help='Interval in seconds to poll gpsd and write to file. Defaults '
153 | 'to 5.'
154 | )
155 | p.add_argument(
156 | '--no-flush', dest='FLUSH_FILE', action='store_true', default=False,
157 | help='Do not explicitly flush file after writing each record.'
158 | )
159 | cwd = os.getcwd()
160 | p.add_argument(
161 | '-d', '--out-dir', dest='OUT_DIR', action='store', type=str,
162 | default=cwd,
163 | help='Directory to write output files in. Default is your current '
164 | 'working directory (%s)' % cwd
165 | )
166 | user = os.environ.get('SUDO_UID', '%d' % os.geteuid())
167 | p.add_argument(
168 | '-u', '--user', dest='user', action='store', type=str, default=user,
169 | help='User to run daemon as (default: %s)' % user
170 | )
171 | group = os.environ.get('SUDO_GID', '%d' % os.getegid())
172 | p.add_argument(
173 | '-G', '--group', dest='group', action='store', type=str, default=group,
174 | help='Group to run daemon as (default: %s)' % group
175 | )
176 | args = p.parse_args(argv)
177 | return args
178 |
179 |
180 | def main():
181 | args = parse_args(sys.argv[1:])
182 |
183 | # set logging level
184 | if args.verbose:
185 | set_log_debug(logger)
186 |
187 | Installer(args).run()
188 |
189 |
190 | if __name__ == "__main__":
191 | main()
192 |
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/gpsd/bu303-climbing.log.chk:
--------------------------------------------------------------------------------
1 | $GPGSV,2,1,07,10,45,196,10,29,67,310,42,28,59,108,40,26,51,304,44*70
2 | $GPGSV,2,2,07,08,44,058,43,27,16,066,37,21,10,301,00*4A
3 | {"class":"SKY","time":"2005-06-19T16:12:22.890Z","xdop":1.02,"ydop":1.08,"vdop":2.56,"tdop":1.96,"hdop":1.48,"gdop":3.55,"pdop":2.96,"satellites":[{"PRN":10,"el":45,"az":196,"ss":10,"used":true},{"PRN":29,"el":67,"az":310,"ss":42,"used":true},{"PRN":28,"el":59,"az":108,"ss":40,"used":true},{"PRN":26,"el":51,"az":304,"ss":44,"used":true},{"PRN":8,"el":44,"az":58,"ss":43,"used":true},{"PRN":27,"el":16,"az":66,"ss":37,"used":true},{"PRN":21,"el":10,"az":301,"ss":0,"used":false}]}
4 | $GPZDA,161222.89,19,06,2005,00,00*6A
5 | $GPGGA,161222,4629.8923,N,00734.0837,E,1,06,3.20,1327.69,M,48.183,M,,*7A
6 | $GPRMC,161222,A,4629.8923,N,00734.0837,E,0.1673,180.000,190605,,*2D
7 | $GPGSA,A,3,10,29,28,26,8,27,,,,,,,3.0,3.2,2.6*0D
8 | $GPGBS,161222,15.28,M,16.17,M,58.85,M*05
9 | {"class":"TPV","mode":3,"time":"2005-06-19T16:12:22.890Z","ept":0.005,"lat":46.498204497,"lon":7.568061439,"alt":1327.689,"epx":15.279,"epy":16.167,"epv":58.845,"track":180.0000,"speed":0.086,"climb":-0.091}
10 | $GPGSV,2,1,07,10,45,196,08,29,67,310,41,28,59,108,40,26,51,304,43*7D
11 | $GPGSV,2,2,07,08,44,058,42,27,16,066,36,21,10,301,00*4A
12 | {"class":"SKY","time":"2005-06-19T16:12:23.890Z","xdop":1.02,"ydop":1.08,"vdop":2.56,"tdop":1.96,"hdop":3.20,"gdop":3.55,"pdop":2.96,"satellites":[{"PRN":10,"el":45,"az":196,"ss":8,"used":true},{"PRN":29,"el":67,"az":310,"ss":41,"used":true},{"PRN":28,"el":59,"az":108,"ss":40,"used":true},{"PRN":26,"el":51,"az":304,"ss":43,"used":true},{"PRN":8,"el":44,"az":58,"ss":42,"used":true},{"PRN":27,"el":16,"az":66,"ss":36,"used":true},{"PRN":21,"el":10,"az":301,"ss":0,"used":false}]}
13 | $GPZDA,161223.89,19,06,2005,00,00*6B
14 | $GPGGA,161223,4629.8923,N,00734.0837,E,1,06,3.20,1327.69,M,48.183,M,,*7B
15 | $GPRMC,161223,A,4629.8923,N,00734.0837,E,0.1776,10.380,190605,,*1B
16 | $GPGSA,A,3,10,29,28,26,8,27,,,,,,,3.0,3.2,2.6*0D
17 | {"class":"TPV","mode":3,"time":"2005-06-19T16:12:23.890Z","ept":0.005,"lat":46.498204497,"lon":7.568061439,"alt":1327.689,"epx":15.279,"epy":16.167,"epv":58.845,"track":10.3797,"speed":0.091,"climb":-0.085,"eps":32.33,"epc":117.69}
18 | $GPGSV,2,1,07,10,45,196,33,29,67,310,42,28,59,108,42,26,51,304,43*74
19 | $GPGSV,2,2,07,08,44,058,44,27,16,066,36,21,10,301,00*4C
20 | {"class":"SKY","time":"2005-06-19T16:12:24.890Z","xdop":1.02,"ydop":1.08,"vdop":2.56,"tdop":1.96,"hdop":3.20,"gdop":3.55,"pdop":2.96,"satellites":[{"PRN":10,"el":45,"az":196,"ss":33,"used":true},{"PRN":29,"el":67,"az":310,"ss":42,"used":true},{"PRN":28,"el":59,"az":108,"ss":42,"used":true},{"PRN":26,"el":51,"az":304,"ss":43,"used":true},{"PRN":8,"el":44,"az":58,"ss":44,"used":true},{"PRN":27,"el":16,"az":66,"ss":36,"used":true},{"PRN":21,"el":10,"az":301,"ss":0,"used":false}]}
21 | $GPZDA,161224.89,19,06,2005,00,00*6C
22 | $GPGGA,161224,4629.8923,N,00734.0837,E,1,06,1.40,1327.69,M,48.183,M,,*78
23 | $GPRMC,161224,A,4629.8923,N,00734.0837,E,0.1673,180.000,190605,,*2B
24 | $GPGSA,A,3,10,29,28,26,8,27,,,,,,,3.0,1.4,2.6*09
25 | {"class":"TPV","mode":3,"time":"2005-06-19T16:12:24.890Z","ept":0.005,"lat":46.498204497,"lon":7.568061439,"alt":1327.689,"epx":15.279,"epy":16.167,"epv":58.845,"track":180.0000,"speed":0.086,"climb":-0.091,"eps":32.33,"epc":117.69}
26 | $GPGSV,2,1,07,10,45,196,31,29,67,310,43,28,59,108,42,26,51,304,45*71
27 | $GPGSV,2,2,07,08,44,058,46,27,16,066,42,21,10,301,00*4D
28 | {"class":"SKY","time":"2005-06-19T16:12:25.890Z","xdop":1.02,"ydop":1.08,"vdop":2.56,"tdop":1.96,"hdop":1.40,"gdop":3.55,"pdop":2.96,"satellites":[{"PRN":10,"el":45,"az":196,"ss":31,"used":true},{"PRN":29,"el":67,"az":310,"ss":43,"used":true},{"PRN":28,"el":59,"az":108,"ss":42,"used":true},{"PRN":26,"el":51,"az":304,"ss":45,"used":true},{"PRN":8,"el":44,"az":58,"ss":46,"used":true},{"PRN":27,"el":16,"az":66,"ss":42,"used":true},{"PRN":21,"el":10,"az":301,"ss":0,"used":false}]}
29 | $GPZDA,161225.89,19,06,2005,00,00*6D
30 | $GPGGA,161225,4629.8923,N,00734.0837,E,1,06,1.40,1327.69,M,48.183,M,,*79
31 | $GPRMC,161225,A,4629.8923,N,00734.0837,E,0.0000,0.000,190605,,*20
32 | $GPGSA,A,3,10,29,28,26,8,27,,,,,,,3.0,1.4,2.6*09
33 | {"class":"TPV","mode":3,"time":"2005-06-19T16:12:25.890Z","ept":0.005,"lat":46.498204497,"lon":7.568061439,"alt":1327.689,"epx":15.279,"epy":16.167,"epv":58.845,"track":0.0000,"speed":0.000,"climb":0.000,"eps":32.33,"epc":117.69}
34 | $GPGSV,2,1,07,10,45,196,33,29,67,310,40,28,59,108,41,26,51,304,43*75
35 | $GPGSV,2,2,07,08,44,058,44,27,16,066,40,21,10,301,00*4D
36 | {"class":"SKY","time":"2005-06-19T16:12:26.890Z","xdop":1.02,"ydop":1.08,"vdop":2.56,"tdop":1.96,"hdop":1.40,"gdop":3.55,"pdop":2.96,"satellites":[{"PRN":10,"el":45,"az":196,"ss":33,"used":true},{"PRN":29,"el":67,"az":310,"ss":40,"used":true},{"PRN":28,"el":59,"az":108,"ss":41,"used":true},{"PRN":26,"el":51,"az":304,"ss":43,"used":true},{"PRN":8,"el":44,"az":58,"ss":44,"used":true},{"PRN":27,"el":16,"az":66,"ss":40,"used":true},{"PRN":21,"el":10,"az":301,"ss":0,"used":false}]}
37 | $GPZDA,161226.89,19,06,2005,00,00*6E
38 | $GPGGA,161226,4629.8919,N,00734.0837,E,1,06,1.40,1326.96,M,48.183,M,,*72
39 | $GPRMC,161226,A,4629.8919,N,00734.0837,E,0.1673,180.000,190605,,*20
40 | $GPGSA,A,3,10,29,28,26,8,27,,,,,,,3.0,1.4,2.6*09
41 | {"class":"TPV","mode":3,"time":"2005-06-19T16:12:26.890Z","ept":0.005,"lat":46.498198306,"lon":7.568061439,"alt":1326.964,"epx":15.279,"epy":16.167,"epv":58.845,"track":180.0000,"speed":0.086,"climb":-0.091,"eps":32.33,"epc":117.69}
42 | $GPGSV,2,1,07,10,45,196,34,29,67,310,40,28,59,108,43,26,51,304,43*70
43 | $GPGSV,2,2,07,08,44,058,42,27,16,066,39,21,10,301,00*45
44 | {"class":"SKY","time":"2005-06-19T16:12:27.890Z","xdop":1.02,"ydop":1.08,"vdop":2.56,"tdop":1.96,"hdop":1.40,"gdop":3.55,"pdop":2.96,"satellites":[{"PRN":10,"el":45,"az":196,"ss":34,"used":true},{"PRN":29,"el":67,"az":310,"ss":40,"used":true},{"PRN":28,"el":59,"az":108,"ss":43,"used":true},{"PRN":26,"el":51,"az":304,"ss":43,"used":true},{"PRN":8,"el":44,"az":58,"ss":42,"used":true},{"PRN":27,"el":16,"az":66,"ss":39,"used":true},{"PRN":21,"el":10,"az":301,"ss":0,"used":false}]}
45 | $GPZDA,161227.89,19,06,2005,00,00*6F
46 | $GPGGA,161227,4629.8919,N,00734.0837,E,1,06,1.40,1326.96,M,48.183,M,,*73
47 | $GPRMC,161227,A,4629.8919,N,00734.0837,E,0.0000,0.000,190605,,*2B
48 | $GPGSA,A,3,10,29,28,26,8,27,,,,,,,3.0,1.4,2.6*09
49 | {"class":"TPV","mode":3,"time":"2005-06-19T16:12:27.890Z","ept":0.005,"lat":46.498198306,"lon":7.568061439,"alt":1326.964,"epx":15.279,"epy":16.167,"epv":58.845,"track":0.0000,"speed":0.000,"climb":0.000,"eps":32.33,"epc":117.69}
50 | $GPGSV,2,1,07,10,45,196,35,29,67,310,39,28,59,108,43,26,51,304,43*7F
51 | $GPGSV,2,2,07,08,44,058,42,27,16,066,38,21,10,301,00*44
52 | {"class":"SKY","time":"2005-06-19T16:12:28.890Z","xdop":1.02,"ydop":1.08,"vdop":2.56,"tdop":1.96,"hdop":1.40,"gdop":3.55,"pdop":2.96,"satellites":[{"PRN":10,"el":45,"az":196,"ss":35,"used":true},{"PRN":29,"el":67,"az":310,"ss":39,"used":true},{"PRN":28,"el":59,"az":108,"ss":43,"used":true},{"PRN":26,"el":51,"az":304,"ss":43,"used":true},{"PRN":8,"el":44,"az":58,"ss":42,"used":true},{"PRN":27,"el":16,"az":66,"ss":38,"used":true},{"PRN":21,"el":10,"az":301,"ss":0,"used":false}]}
53 | $GPZDA,161228.89,19,06,2005,00,00*60
54 | $GPGGA,161228,4629.8919,N,00734.0837,E,1,06,1.40,1326.96,M,48.183,M,,*7C
55 | $GPRMC,161228,A,4629.8919,N,00734.0837,E,0.0000,0.000,190605,,*24
56 | $GPGSA,A,3,10,29,28,26,8,27,,,,,,,3.0,1.4,2.6*09
57 | {"class":"TPV","mode":3,"time":"2005-06-19T16:12:28.890Z","ept":0.005,"lat":46.498198306,"lon":7.568061439,"alt":1326.964,"epx":15.279,"epy":16.167,"epv":58.845,"track":0.0000,"speed":0.000,"climb":0.000,"eps":32.33,"epc":117.69}
58 | $GPGSV,2,1,07,10,45,196,37,29,67,310,40,28,59,108,45,26,51,304,42*74
59 | $GPGSV,2,2,07,08,44,058,42,27,16,066,38,21,10,301,00*44
60 | {"class":"SKY","time":"2005-06-19T16:12:29.890Z","xdop":1.02,"ydop":1.08,"vdop":2.56,"tdop":1.96,"hdop":1.40,"gdop":3.55,"pdop":2.96,"satellites":[{"PRN":10,"el":45,"az":196,"ss":37,"used":true},{"PRN":29,"el":67,"az":310,"ss":40,"used":true},{"PRN":28,"el":59,"az":108,"ss":45,"used":true},{"PRN":26,"el":51,"az":304,"ss":42,"used":true},{"PRN":8,"el":44,"az":58,"ss":42,"used":true},{"PRN":27,"el":16,"az":66,"ss":38,"used":true},{"PRN":21,"el":10,"az":301,"ss":0,"used":false}]}
61 | $GPZDA,161229.89,19,06,2005,00,00*61
62 | $GPGGA,161229,4629.8919,N,00734.0837,E,1,06,1.40,1326.96,M,48.183,M,,*7D
63 | $GPRMC,161229,A,4629.8919,N,00734.0837,E,0.0000,0.000,190605,,*25
64 | $GPGSA,A,3,10,29,28,26,8,27,,,,,,,3.0,1.4,2.6*09
65 | {"class":"TPV","mode":3,"time":"2005-06-19T16:12:29.890Z","ept":0.005,"lat":46.498198306,"lon":7.568061439,"alt":1326.964,"epx":15.279,"epy":16.167,"epv":58.845,"track":0.0000,"speed":0.000,"climb":0.000,"eps":32.33,"epc":117.69}
66 |
--------------------------------------------------------------------------------
/pizero_gpslog/runner.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
38 | import os
39 | import logging
40 | import time
41 | import json
42 | from typing import Optional
43 | from _io import TextIOWrapper
44 | from importlib import import_module
45 |
46 | from pizero_gpslog.gpsd import (
47 | GpsClient, NoActiveGpsError, NoFixError, GpsResponse
48 | )
49 | from pizero_gpslog.version import VERSION, PROJECT_URL
50 | from pizero_gpslog.utils import set_log_info, set_log_debug, FixType
51 | from pizero_gpslog.displaymanager import DisplayManager
52 | from pizero_gpslog.extradata.base import BaseExtraDataProvider
53 |
54 | if 'LED_PIN_RED' in os.environ and 'LED_PIN_GREEN' in os.environ:
55 | from gpiozero import LED
56 | else:
57 | from pizero_gpslog.fakeled import FakeLed as LED
58 |
59 | logger = logging.getLogger(__name__)
60 |
61 |
62 | class EmptyExtraData:
63 |
64 | def __init__(self):
65 | self.data = {'message': ''}
66 |
67 | def start(self):
68 | pass
69 |
70 |
71 | class GpsLogger(object):
72 |
73 | def __init__(self):
74 | logger.warning(
75 | 'Starting pizero-gpslog version %s <%s>', VERSION, PROJECT_URL
76 | )
77 | led_1_pin: int = int(os.environ.get('LED_PIN_RED', '-1'))
78 | logger.info('Initializing LED1 (Red) on pin %d', led_1_pin)
79 | self.LED1: LED = LED(led_1_pin)
80 | led_2_pin: int = int(os.environ.get('LED_PIN_GREEN', '-2'))
81 | logger.info('Initializing LED2 (Green) on pin %d', led_2_pin)
82 | self.LED2: LED = LED(led_2_pin)
83 | self.LED2.on()
84 | logger.info('Connecting to gpsd')
85 | self.gps: GpsClient = GpsClient()
86 | self.interval_sec: int = int(os.environ.get('GPS_INTERVAL_SEC', '5'))
87 | logger.info('Sleeping %s seconds between writes', self.interval_sec)
88 | self.flush_file: str = os.environ.get('FLUSH_FILE', '') != 'false'
89 | self.outdir: str = os.path.abspath(
90 | os.environ.get('OUT_DIR', os.getcwd())
91 | )
92 | logger.debug('Writing logs in: %s', self.outdir)
93 | self._fh: Optional[TextIOWrapper] = None
94 | self._display: Optional[DisplayManager] = None
95 | if 'DISPLAY_CLASS' in os.environ:
96 | modname, clsname = os.environ['DISPLAY_CLASS'].split(':')
97 | self._display = DisplayManager(modname, clsname)
98 | self._display.set_fix_type(FixType.NO_GPS)
99 | self._display.start()
100 | if 'EXTRA_DATA_CLASS' in os.environ:
101 | modname, clsname = os.environ['EXTRA_DATA_CLASS'].split(':')
102 | logger.debug('Import %s:%s', modname, clsname)
103 | mod = import_module(modname)
104 | extra_cls: BaseExtraDataProvider.__class__ = getattr(
105 | mod, clsname
106 | )
107 | self._extra_data_instance = extra_cls()
108 | self._extra_data_instance.start()
109 | else:
110 | self._extra_data_instance = EmptyExtraData()
111 |
112 | def run(self):
113 | self.LED2.off()
114 | while True:
115 | time.sleep(self.interval_sec)
116 | logger.debug('Reading current position from gpsd')
117 | try:
118 | packet = self.gps.current_fix
119 | except NoActiveGpsError:
120 | packet = GpsResponse()
121 | packet.mode = 0
122 | except NoFixError:
123 | packet = GpsResponse()
124 | packet.mode = 1
125 | self._handle_packet(packet)
126 |
127 | def _handle_waiting_gps(self, packet: GpsResponse):
128 | logger.warning(
129 | 'No data returned by gpsd (no active GPS) - %s',
130 | packet
131 | )
132 | if not self.LED1.is_lit:
133 | self.LED1.on()
134 | if self._display is not None:
135 | self._display.set_fix_type(FixType.NO_GPS)
136 | self._display.set_extradata(
137 | self._extra_data_instance.data.get('message', '')
138 | )
139 |
140 | def _handle_no_fix(self, packet: GpsResponse):
141 | logger.warning('No GPS fix yet - %s', packet)
142 | self.LED1.blink(on_time=0.1, off_time=0.1, n=3)
143 | if self._display is not None:
144 | self._display.set_fix_type(FixType.NO_FIX)
145 | self._display.set_extradata(
146 | self._extra_data_instance.data.get('message', '')
147 | )
148 |
149 | def _ensure_file_open(self, packet: GpsResponse):
150 | if self._fh is not None:
151 | return
152 | logger.info(
153 | 'Got GPS packet with fix; GPS time is %s (UTC)'
154 | '' % packet.get_time()
155 | )
156 | outfile = os.path.join(
157 | self.outdir,
158 | '%s.json' % packet.get_time().strftime('%Y-%m-%d_%H-%M-%S')
159 | )
160 | logger.info('Writing output to: %s', outfile)
161 | self._fh = open(outfile, 'w', buffering=1)
162 |
163 | def _handle_fix(self, packet: GpsResponse):
164 | logger.info(packet)
165 | if packet.mode == 2:
166 | self.LED1.blink(on_time=0.5, off_time=0.25, n=2)
167 | if self._display is not None:
168 | self._display.set_fix_type(FixType.FIX_2D)
169 | elif packet.mode == 3:
170 | self.LED1.blink(on_time=0.5, off_time=0.25, n=1)
171 | if self._display is not None:
172 | self._display.set_fix_type(FixType.FIX_3D)
173 | if self._display is not None:
174 | self._display.set_fix_precision(packet.position_precision())
175 | lat, lon = packet.position()
176 | self._display.set_lat(lat)
177 | self._display.set_lon(lon)
178 | self._display.set_extradata(
179 | self._extra_data_instance.data.get('message', '')
180 | )
181 |
182 | def _handle_packet(self, packet: GpsResponse):
183 | if packet.mode == 0:
184 | return self._handle_waiting_gps(packet)
185 | if self.LED1.is_lit:
186 | self.LED1.off()
187 | if packet.mode == 1:
188 | return self._handle_no_fix(packet)
189 | # else we have a fix
190 | self._ensure_file_open(packet)
191 | if packet.mode in [2, 3]:
192 | self._handle_fix(packet)
193 | if self._extra_data_instance is not None:
194 | packet.raw_packet['_extra_data'] = self._extra_data_instance.data
195 | self._fh.write('%s\n' % json.dumps(packet.raw_packet))
196 | if self.flush_file:
197 | self._fh.flush()
198 | self.LED2.blink(on_time=0.25, off_time=0.25, n=1)
199 |
200 |
201 | def main():
202 | global logger
203 | format = "[%(asctime)s %(levelname)s] %(message)s"
204 | logging.basicConfig(level=logging.WARNING, format=format)
205 | logger = logging.getLogger()
206 |
207 | # set logging level
208 | if os.environ.get('LOG_LEVEL', None) == 'DEBUG':
209 | set_log_debug(logger)
210 | elif os.environ.get('LOG_LEVEL', None) == 'INFO':
211 | set_log_info(logger)
212 | GpsLogger().run()
213 |
214 |
215 | if __name__ == "__main__":
216 | main()
217 |
--------------------------------------------------------------------------------
/pizero_gpslog/converter.py:
--------------------------------------------------------------------------------
1 | """
2 | The latest version of this package is available at:
3 |
4 |
5 | ##################################################################################
6 | Copyright 2018-2020 Jason Antman
7 |
8 | This file is part of pizero-gpslog, also known as pizero-gpslog.
9 |
10 | pizero-gpslog is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pizero-gpslog is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Affero General Public License for more details.
19 |
20 | You should have received a copy of the GNU Affero General Public License
21 | along with pizero-gpslog. If not, see .
22 |
23 | The Copyright and Authors attributions contained herein may not be removed or
24 | otherwise altered, except to add the Author attribution of a contributor to
25 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
26 | ##################################################################################
27 | While not legally required, I sincerely request that anyone who finds
28 | bugs please submit them at or
29 | to me via email, and that you send any contributions or improvements
30 | either as a pull request on GitHub, or to me via email.
31 | ##################################################################################
32 |
33 | AUTHORS:
34 | Jason Antman
35 | ##################################################################################
36 | """
37 |
38 | import sys
39 | import argparse
40 | import json
41 |
42 | import pint
43 | from gpxpy.gpx import GPX, GPXTrack, GPXTrackSegment, GPXTrackPoint
44 | from gpxpy.gpxfield import TIME_TYPE
45 |
46 | from pizero_gpslog.version import VERSION
47 |
48 |
49 | class GpxConverter(object):
50 |
51 | def __init__(self, input_fpath, imperial=False):
52 | self._in_fpath = input_fpath
53 | self._imperial = imperial
54 | self._ureg = pint.UnitRegistry()
55 |
56 | def convert(self):
57 | logs = []
58 | with open(self._in_fpath, 'r', errors='ignore') as fh:
59 | lineno = 0
60 | for line in fh.readlines():
61 | lineno += 1
62 | line = line.strip()
63 | if len(line) == 0:
64 | continue
65 | try:
66 | j = json.loads(line)
67 | except json.decoder.JSONDecodeError as ex:
68 | sys.stderr.write(
69 | 'Unable to decode JSON on line %s; skipping. '
70 | '(ERROR: %s)\n' % (
71 | lineno, ex
72 | )
73 | )
74 | continue
75 | if 'tpv' not in j:
76 | continue
77 | if j['tpv'][0].get('mode', 0) < 2:
78 | continue
79 | j['lineno'] = lineno
80 | logs.append(j)
81 | gpx = self._gpx_for_logs(logs)
82 | return gpx
83 |
84 | def stats_for_gpx(self, gpx):
85 | cloned_gpx = gpx.clone()
86 | cloned_gpx.reduce_points(2000, min_distance=10)
87 | cloned_gpx.smooth(vertical=True, horizontal=True)
88 | cloned_gpx.smooth(vertical=True, horizontal=False)
89 | moving_time, stopped_time, moving_distance, stopped_distance, \
90 | max_speed_ms = cloned_gpx.get_moving_data()
91 | ud = gpx.get_uphill_downhill()
92 | elev = gpx.get_elevation_extremes()
93 | return {
94 | 'track_start': gpx.get_time_bounds().start_time,
95 | 'track_end': gpx.get_time_bounds().end_time,
96 | 'duration_sec': gpx.get_duration(),
97 | 'num_points': gpx.get_points_no(),
98 | 'moving_time': moving_time,
99 | 'stopped_time': stopped_time,
100 | 'moving_distance': moving_distance,
101 | 'stopped_distance': stopped_distance,
102 | 'max_speed_ms': max_speed_ms,
103 | '2d_horizontal_distance': gpx.length_2d(),
104 | 'total_elev_inc': ud.uphill,
105 | 'total_elev_dec': ud.downhill,
106 | 'min_elev': elev.minimum,
107 | 'max_elev': elev.maximum
108 | }
109 |
110 | def stats_text(self, stats):
111 | s = 'Track Start: %s UTC\n' % stats['track_start']
112 | s += 'Track End: %s UTC\n' % stats['track_end']
113 | s += 'Track Duration: %s\n' % seconds(stats['duration_sec'])
114 | s += '%d points in track\n' % stats['num_points']
115 | s += 'Moving time: %s\n' % seconds(stats['moving_time'])
116 | s += 'Stopped time: %s\n' % seconds(stats['stopped_time'])
117 | s += 'Max Speed: %s\n' % self._ms_mph(stats['max_speed_ms'])
118 | s += '2D (Horizontal) distance: %s\n' % self._m_ftmi(
119 | stats['2d_horizontal_distance']
120 | )
121 | s += 'Total elevation increase: %s\n' % self._m_ft(
122 | stats['total_elev_inc']
123 | )
124 | s += 'Total elevation decrease: %s\n' % self._m_ft(
125 | stats['total_elev_dec']
126 | )
127 | s += 'Minimum elevation: %s\n' % self._m_ft(stats['min_elev'])
128 | s += 'Maximum elevation: %s\n' % self._m_ft(stats['max_elev'])
129 | return s
130 |
131 | def _gpx_for_logs(self, logs):
132 | g = GPX()
133 | track = GPXTrack()
134 | track.source = 'pizero-gpslog %s' % VERSION
135 | g.tracks.append(track)
136 | seg = GPXTrackSegment()
137 | track.segments.append(seg)
138 | prev_alt = 0.0
139 |
140 | for item in logs:
141 | try:
142 | tpv = item['tpv'][0]
143 | sky = item['sky'][0]
144 | alt = tpv.get(
145 | 'alt', item['gst'][0].get('alt', prev_alt)
146 | )
147 | prev_alt = alt
148 | p = GPXTrackPoint(
149 | latitude=tpv['lat'],
150 | longitude=tpv['lon'],
151 | elevation=alt,
152 | time=TIME_TYPE.from_string(tpv['time']),
153 | speed=tpv['speed'],
154 | horizontal_dilution=sky.get('hdop', None),
155 | vertical_dilution=sky.get('vdop', None),
156 | position_dilution=sky.get('pdop', None)
157 | )
158 | if tpv['mode'] == 2:
159 | p.type_of_gpx_fix = '2d'
160 | elif tpv['mode'] == 3:
161 | p.type_of_gpx_fix = '3d'
162 | if 'satellites' in sky:
163 | p.satellites = len(sky['satellites'])
164 | seg.points.append(p)
165 | except Exception:
166 | sys.stderr.write(
167 | 'Exception loading line %d:\n' % item['lineno']
168 | )
169 | raise
170 | return g
171 |
172 | def _ms_mph(self, n):
173 | if not self._imperial:
174 | return '%.4f m/s' % n
175 | val = n * self._ureg.meter / self._ureg.second
176 | return '%.4f MPH' % val.to(self._ureg.mile / self._ureg.hour).magnitude
177 |
178 | def _m_ftmi(self, n):
179 | if not self._imperial:
180 | return '%.4f m' % n
181 | val = n * self._ureg.meter
182 | val = val.to(self._ureg.mile)
183 | return '%.4f Mi' % val.magnitude
184 |
185 | def _m_ft(self, n):
186 | if not self._imperial:
187 | return '%.4f m' % n
188 | val = n * self._ureg.meter
189 | val = val.to(self._ureg.foot)
190 | return '%.4f ft' % val.magnitude
191 |
192 |
193 | def seconds(s):
194 | res = []
195 | if s > 3600:
196 | h, s = divmod(s, 3600)
197 | res.append('%dh' % h)
198 | if s > 60:
199 | m, s = divmod(s, 60)
200 | res.append('%dm' % m)
201 | res.append('%ds' % s)
202 | return ' '.join(res)
203 |
204 |
205 | def main(argv=sys.argv[1:]):
206 | args = parse_args(argv)
207 | if args.output is None:
208 | if '.' not in args.JSON_FILE:
209 | args.output = args.JSON_FILE + '.' + args.format
210 | else:
211 | args.output = args.JSON_FILE.rsplit('.', 1)[0] + '.' + args.format
212 | conv = GpxConverter(args.JSON_FILE, imperial=args.imperial)
213 | gpx = conv.convert()
214 | with open(args.output, 'w') as fh:
215 | fh.write(gpx.to_xml())
216 | sys.stderr.write('GPX file written to: %s' % args.output)
217 | if args.stats:
218 | print(conv.stats_text(conv.stats_for_gpx(gpx)))
219 |
220 |
221 | def parse_args(argv):
222 | """parse arguments/options"""
223 | p = argparse.ArgumentParser(
224 | description='Convert pizero-gpslog (gpsd POLL format) output files to '
225 | 'common GPS formats.'
226 | )
227 | p.add_argument('-f', '--format', dest='format', action='store', type=str,
228 | choices=['gpx'], default='gpx',
229 | help='destination format (default: gpx)')
230 | p.add_argument('-o', '--output', dest='output', action='store', type=str,
231 | default=None,
232 | help='Output file path. By default, will be the input '
233 | 'file path with the file extension replaced with the '
234 | 'correct one for the output format.')
235 | p.add_argument('-S', '--no-stats', dest='stats', action='store_false',
236 | default=True,
237 | help='do not print stats to STDERR'
238 | )
239 | p.add_argument('-i', '--imperial', dest='imperial', action='store_true',
240 | default=False, help='output stats in imperial units')
241 | p.add_argument('JSON_FILE', action='store', type=str,
242 | help='Input file to convert')
243 | args = p.parse_args(argv)
244 | return args
245 |
246 |
247 | if __name__ == '__main__':
248 | main(sys.argv[1:])
249 |
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/gpsd/bu303-stillfix.log.chk:
--------------------------------------------------------------------------------
1 | $GPGSV,2,1,08,23,07,084,00,28,07,160,00,08,65,189,45,29,13,273,00*77
2 | $GPGSV,2,2,08,10,50,304,37,04,16,199,36,02,34,241,43,27,71,076,43*71
3 | {"class":"SKY","time":"2005-06-09T14:34:11.280Z","xdop":1.68,"ydop":1.65,"vdop":3.49,"tdop":3.10,"hdop":2.35,"gdop":5.23,"pdop":4.21,"satellites":[{"PRN":23,"el":7,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":65,"az":189,"ss":45,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":50,"az":304,"ss":37,"used":true},{"PRN":4,"el":16,"az":199,"ss":36,"used":true},{"PRN":2,"el":34,"az":241,"ss":43,"used":true},{"PRN":27,"el":71,"az":76,"ss":43,"used":true}]}
4 | $GPZDA,143411.28,09,06,2005,00,00*66
5 | $GPGGA,143411,4629.8901,N,00734.0471,E,1,05,2.40,1349.51,M,48.183,M,,*75
6 | $GPRMC,143411,A,4629.8901,N,00734.0471,E,0.1776,10.379,090605,,*15
7 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.5*0E
8 | $GPGBS,143411,25.19,M,24.69,M,80.26,M*06
9 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:11.280Z","ept":0.005,"lat":46.498167579,"lon":7.567452213,"alt":1349.507,"epx":25.195,"epy":24.691,"epv":80.261,"track":10.3789,"speed":0.091,"climb":-0.085}
10 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,65,189,43,29,13,273,00*70
11 | $GPGSV,2,2,08,10,50,304,36,04,16,199,36,02,34,241,44,27,71,076,43*77
12 | {"class":"SKY","time":"2005-06-09T14:34:12.280Z","xdop":1.68,"ydop":1.65,"vdop":3.49,"tdop":3.10,"hdop":2.40,"gdop":5.23,"pdop":4.21,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":65,"az":189,"ss":43,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":50,"az":304,"ss":36,"used":true},{"PRN":4,"el":16,"az":199,"ss":36,"used":true},{"PRN":2,"el":34,"az":241,"ss":44,"used":true},{"PRN":27,"el":71,"az":76,"ss":43,"used":true}]}
13 | $GPZDA,143412.28,09,06,2005,00,00*65
14 | $GPGGA,143412,4629.8905,N,00734.0473,E,1,05,2.40,1347.42,M,48.183,M,,*7C
15 | $GPRMC,143412,A,4629.8905,N,00734.0473,E,0.1776,10.379,090605,,*10
16 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.5*0E
17 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:12.280Z","ept":0.005,"lat":46.498174322,"lon":7.567455643,"alt":1347.417,"epx":25.195,"epy":24.691,"epv":80.261,"track":10.3789,"speed":0.091,"climb":-0.085,"eps":50.39,"epc":160.52}
18 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,65,189,44,29,13,273,00*77
19 | $GPGSV,2,2,08,10,50,304,38,04,16,199,35,02,34,241,44,27,71,076,42*7B
20 | {"class":"SKY","time":"2005-06-09T14:34:13.280Z","xdop":1.68,"ydop":1.65,"vdop":3.49,"tdop":3.10,"hdop":2.40,"gdop":5.23,"pdop":4.21,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":65,"az":189,"ss":44,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":50,"az":304,"ss":38,"used":true},{"PRN":4,"el":16,"az":199,"ss":35,"used":true},{"PRN":2,"el":34,"az":241,"ss":44,"used":true},{"PRN":27,"el":71,"az":76,"ss":42,"used":true}]}
21 | $GPZDA,143413.28,09,06,2005,00,00*64
22 | $GPGGA,143413,4629.8908,N,00734.0474,E,1,05,2.40,1346.73,M,48.183,M,,*74
23 | $GPRMC,143413,A,4629.8908,N,00734.0474,E,0.0000,0.000,090605,,*20
24 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.5*0E
25 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:13.280Z","ept":0.005,"lat":46.498180789,"lon":7.567457358,"alt":1346.734,"epx":25.195,"epy":24.691,"epv":80.261,"track":0.0000,"speed":0.000,"climb":0.000,"eps":50.39,"epc":160.52}
26 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,65,189,45,29,13,273,00*76
27 | $GPGSV,2,2,08,10,50,304,38,04,16,199,35,02,34,241,43,27,71,076,43*7D
28 | {"class":"SKY","time":"2005-06-09T14:34:14.280Z","xdop":1.68,"ydop":1.65,"vdop":3.49,"tdop":3.10,"hdop":2.40,"gdop":5.23,"pdop":4.21,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":65,"az":189,"ss":45,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":50,"az":304,"ss":38,"used":true},{"PRN":4,"el":16,"az":199,"ss":35,"used":true},{"PRN":2,"el":34,"az":241,"ss":43,"used":true},{"PRN":27,"el":71,"az":76,"ss":43,"used":true}]}
29 | $GPZDA,143414.28,09,06,2005,00,00*63
30 | $GPGGA,143414,4629.8912,N,00734.0475,E,1,05,2.40,1346.05,M,48.183,M,,*78
31 | $GPRMC,143414,A,4629.8912,N,00734.0475,E,0.1776,10.379,090605,,*16
32 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.5*0E
33 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:14.280Z","ept":0.005,"lat":46.498187256,"lon":7.567459073,"alt":1346.052,"epx":25.195,"epy":24.691,"epv":80.261,"track":10.3789,"speed":0.091,"climb":-0.085,"eps":50.39,"epc":160.52}
34 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,65,189,44,29,13,273,00*77
35 | $GPGSV,2,2,08,10,50,304,36,04,16,199,32,02,34,241,39,27,71,076,41*7B
36 | {"class":"SKY","time":"2005-06-09T14:34:15.280Z","xdop":1.68,"ydop":1.65,"vdop":3.49,"tdop":3.10,"hdop":2.40,"gdop":5.23,"pdop":4.21,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":65,"az":189,"ss":44,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":50,"az":304,"ss":36,"used":true},{"PRN":4,"el":16,"az":199,"ss":32,"used":true},{"PRN":2,"el":34,"az":241,"ss":39,"used":true},{"PRN":27,"el":71,"az":76,"ss":41,"used":true}]}
37 | $GPZDA,143415.28,09,06,2005,00,00*62
38 | $GPGGA,143415,4629.8909,N,00734.0475,E,1,05,2.40,1345.33,M,48.183,M,,*75
39 | $GPRMC,143415,A,4629.8909,N,00734.0475,E,0.1776,10.379,090605,,*1D
40 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.5*0E
41 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:15.280Z","ept":0.005,"lat":46.498181065,"lon":7.567459073,"alt":1345.327,"epx":25.195,"epy":24.691,"epv":80.261,"track":10.3789,"speed":0.091,"climb":-0.085,"eps":50.39,"epc":160.52}
42 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,65,189,46,29,13,273,00*75
43 | $GPGSV,2,2,08,10,50,304,38,04,16,199,34,02,34,241,41,27,71,076,41*7C
44 | {"class":"SKY","time":"2005-06-09T14:34:16.280Z","xdop":1.68,"ydop":1.65,"vdop":3.49,"tdop":3.10,"hdop":2.40,"gdop":5.23,"pdop":4.21,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":65,"az":189,"ss":46,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":50,"az":304,"ss":38,"used":true},{"PRN":4,"el":16,"az":199,"ss":34,"used":true},{"PRN":2,"el":34,"az":241,"ss":41,"used":true},{"PRN":27,"el":71,"az":76,"ss":41,"used":true}]}
45 | $GPZDA,143416.28,09,06,2005,00,00*61
46 | $GPGGA,143416,4629.8913,N,00734.0476,E,1,05,2.40,1344.64,M,48.183,M,,*7D
47 | $GPRMC,143416,A,4629.8913,N,00734.0476,E,0.1673,180.000,090605,,*27
48 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.5*0E
49 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:16.280Z","ept":0.005,"lat":46.498187532,"lon":7.567460788,"alt":1344.644,"epx":25.195,"epy":24.691,"epv":80.261,"track":180.0000,"speed":0.086,"climb":-0.091,"eps":50.39,"epc":160.52}
50 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,65,189,46,29,13,273,00*75
51 | $GPGSV,2,2,08,10,50,304,37,04,16,199,36,02,34,241,43,27,71,076,41*73
52 | {"class":"SKY","time":"2005-06-09T14:34:17.280Z","xdop":1.68,"ydop":1.65,"vdop":3.49,"tdop":3.10,"hdop":2.40,"gdop":5.23,"pdop":4.21,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":65,"az":189,"ss":46,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":50,"az":304,"ss":37,"used":true},{"PRN":4,"el":16,"az":199,"ss":36,"used":true},{"PRN":2,"el":34,"az":241,"ss":43,"used":true},{"PRN":27,"el":71,"az":76,"ss":41,"used":true}]}
53 | $GPZDA,143417.28,09,06,2005,00,00*60
54 | $GPGGA,143417,4629.8916,N,00734.0478,E,1,05,2.40,1343.96,M,48.183,M,,*7D
55 | $GPRMC,143417,A,4629.8916,N,00734.0478,E,0.1776,10.379,090605,,*1C
56 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.5*0E
57 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:17.280Z","ept":0.005,"lat":46.498193999,"lon":7.567462504,"alt":1343.962,"epx":25.195,"epy":24.691,"epv":80.261,"track":10.3789,"speed":0.091,"climb":-0.085,"eps":50.39,"epc":160.52}
58 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,65,189,45,29,13,273,00*76
59 | $GPGSV,2,2,08,10,50,304,38,04,16,199,36,02,34,241,42,27,71,076,42*7E
60 | {"class":"SKY","time":"2005-06-09T14:34:18.280Z","xdop":1.68,"ydop":1.65,"vdop":3.49,"tdop":3.10,"hdop":2.40,"gdop":5.23,"pdop":4.21,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":65,"az":189,"ss":45,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":50,"az":304,"ss":38,"used":true},{"PRN":4,"el":16,"az":199,"ss":36,"used":true},{"PRN":2,"el":34,"az":241,"ss":42,"used":true},{"PRN":27,"el":71,"az":76,"ss":42,"used":true}]}
61 | $GPZDA,143418.28,09,06,2005,00,00*6F
62 | $GPGGA,143418,4629.8916,N,00734.0478,E,1,05,2.40,1343.96,M,48.183,M,,*72
63 | $GPRMC,143418,A,4629.8916,N,00734.0478,E,0.0000,0.000,090605,,*28
64 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.5*0E
65 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:18.280Z","ept":0.005,"lat":46.498193999,"lon":7.567462504,"alt":1343.962,"epx":25.195,"epy":24.691,"epv":80.261,"track":0.0000,"speed":0.000,"climb":0.000,"eps":50.39,"epc":160.52}
66 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,65,189,45,29,13,273,00*76
67 | $GPGSV,2,2,08,10,50,304,37,04,16,199,36,02,34,241,42,27,71,076,43*70
68 | {"class":"SKY","time":"2005-06-09T14:34:19.280Z","xdop":1.68,"ydop":1.65,"vdop":3.49,"tdop":3.10,"hdop":2.40,"gdop":5.23,"pdop":4.21,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":65,"az":189,"ss":45,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":50,"az":304,"ss":37,"used":true},{"PRN":4,"el":16,"az":199,"ss":36,"used":true},{"PRN":2,"el":34,"az":241,"ss":42,"used":true},{"PRN":27,"el":71,"az":76,"ss":43,"used":true}]}
69 | $GPZDA,143419.28,09,06,2005,00,00*6E
70 | $GPGGA,143419,4629.8917,N,00734.0470,E,1,05,2.40,1343.87,M,48.183,M,,*7A
71 | $GPRMC,143419,A,4629.8917,N,00734.0470,E,0.0000,0.000,090605,,*20
72 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.5*0E
73 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:19.280Z","ept":0.005,"lat":46.498194858,"lon":7.567449593,"alt":1343.871,"epx":25.195,"epy":24.691,"epv":80.261,"track":0.0000,"speed":0.000,"climb":0.000,"eps":50.39,"epc":160.52}
74 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,65,189,46,29,13,273,00*75
75 | $GPGSV,2,2,08,10,50,304,36,04,16,199,36,02,34,241,42,27,71,076,42*70
76 | {"class":"SKY","time":"2005-06-09T14:34:20.280Z","xdop":1.68,"ydop":1.65,"vdop":3.49,"tdop":3.10,"hdop":2.40,"gdop":5.23,"pdop":4.21,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":65,"az":189,"ss":46,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":50,"az":304,"ss":36,"used":true},{"PRN":4,"el":16,"az":199,"ss":36,"used":true},{"PRN":2,"el":34,"az":241,"ss":42,"used":true},{"PRN":27,"el":71,"az":76,"ss":42,"used":true}]}
77 | $GPZDA,143420.28,09,06,2005,00,00*64
78 | $GPGGA,143420,4629.8921,N,00734.0471,E,1,05,2.40,1343.19,M,48.183,M,,*73
79 | $GPRMC,143420,A,4629.8921,N,00734.0471,E,0.1776,10.379,090605,,*15
80 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.5*0E
81 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:20.280Z","ept":0.005,"lat":46.498201325,"lon":7.567451308,"alt":1343.189,"epx":25.195,"epy":24.691,"epv":80.261,"track":10.3788,"speed":0.091,"climb":-0.085,"eps":50.39,"epc":160.52}
82 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,65,189,46,29,13,273,00*75
83 | $GPGSV,2,2,08,10,50,304,36,04,16,199,37,02,34,241,42,27,71,076,42*71
84 | {"class":"SKY","time":"2005-06-09T14:34:21.280Z","xdop":1.68,"ydop":1.65,"vdop":3.49,"tdop":3.10,"hdop":2.40,"gdop":5.23,"pdop":4.21,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":65,"az":189,"ss":46,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":50,"az":304,"ss":36,"used":true},{"PRN":4,"el":16,"az":199,"ss":37,"used":true},{"PRN":2,"el":34,"az":241,"ss":42,"used":true},{"PRN":27,"el":71,"az":76,"ss":42,"used":true}]}
85 | $GPZDA,143421.28,09,06,2005,00,00*65
86 | $GPGGA,143421,4629.8921,N,00734.0471,E,1,05,2.40,1343.19,M,48.183,M,,*72
87 | $GPRMC,143421,A,4629.8921,N,00734.0471,E,0.1776,10.379,090605,,*14
88 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.5*0E
89 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:21.280Z","ept":0.005,"lat":46.498201325,"lon":7.567451308,"alt":1343.189,"epx":25.195,"epy":24.691,"epv":80.261,"track":10.3788,"speed":0.091,"climb":-0.085,"eps":50.39,"epc":160.52}
90 |
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/gpsd/bu303-moving.log.chk:
--------------------------------------------------------------------------------
1 | $GPZDA,143443.28,09,06,2005,00,00*61
2 | $GPGGA,143443,4629.8972,N,00734.0447,E,1,00,2.40,1342.40,M,48.183,M,,*7D
3 | $GPRMC,143443,A,4629.8972,N,00734.0447,E,0.1776,10.379,090605,,*13
4 | $GPGSA,A,3,,,,,,,,,,,,,,2.4,*34
5 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:43.280Z","ept":0.005,"lat":46.498287178,"lon":7.567411672,"alt":1342.402,"track":10.3788,"speed":0.091,"climb":-0.085}
6 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,66,189,44,29,13,273,00*74
7 | $GPGSV,2,2,08,10,51,304,29,04,15,199,36,02,34,241,43,27,71,076,43*7C
8 | {"class":"SKY","time":"2005-06-09T14:34:44.280Z","xdop":1.66,"ydop":1.69,"vdop":3.42,"tdop":3.05,"hdop":2.40,"gdop":5.15,"pdop":4.16,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":66,"az":189,"ss":44,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":51,"az":304,"ss":29,"used":true},{"PRN":4,"el":15,"az":199,"ss":36,"used":true},{"PRN":2,"el":34,"az":241,"ss":43,"used":true},{"PRN":27,"el":71,"az":76,"ss":43,"used":true}]}
9 | $GPZDA,143444.28,09,06,2005,00,00*66
10 | $GPGGA,143444,4629.8976,N,00734.0447,E,1,05,2.40,1343.13,M,48.183,M,,*7C
11 | $GPRMC,143444,A,4629.8976,N,00734.0447,E,0.1776,10.379,090605,,*10
12 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.4*0F
13 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:44.280Z","ept":0.005,"lat":46.498293369,"lon":7.567411672,"alt":1343.127,"epx":24.829,"epy":25.326,"epv":78.615,"track":10.3788,"speed":0.091,"climb":-0.085,"eps":50.65,"epc":157.23}
14 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,66,189,44,29,13,273,00*74
15 | $GPGSV,2,2,08,10,51,304,28,04,15,199,37,02,34,241,43,27,71,076,43*7C
16 | {"class":"SKY","time":"2005-06-09T14:34:45.280Z","xdop":1.66,"ydop":1.69,"vdop":3.42,"tdop":3.05,"hdop":2.40,"gdop":5.15,"pdop":4.16,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":66,"az":189,"ss":44,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":51,"az":304,"ss":28,"used":true},{"PRN":4,"el":15,"az":199,"ss":37,"used":true},{"PRN":2,"el":34,"az":241,"ss":43,"used":true},{"PRN":27,"el":71,"az":76,"ss":43,"used":true}]}
17 | $GPZDA,143445.28,09,06,2005,00,00*67
18 | $GPGGA,143445,4629.8980,N,00734.0440,E,1,05,2.40,1342.35,M,48.183,M,,*76
19 | $GPRMC,143445,A,4629.8980,N,00734.0440,E,0.1776,10.379,090605,,*1F
20 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.4*0F
21 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:45.280Z","ept":0.005,"lat":46.498300695,"lon":7.567400477,"alt":1342.354,"epx":24.829,"epy":25.326,"epv":78.615,"track":10.3788,"speed":0.091,"climb":-0.085,"eps":50.65,"epc":157.23}
22 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,66,189,44,29,13,273,00*74
23 | $GPGSV,2,2,08,10,51,304,27,04,15,199,35,02,34,241,42,27,71,076,42*71
24 | {"class":"SKY","time":"2005-06-09T14:34:46.280Z","xdop":1.66,"ydop":1.69,"vdop":3.42,"tdop":3.05,"hdop":2.40,"gdop":5.15,"pdop":4.16,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":66,"az":189,"ss":44,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":51,"az":304,"ss":27,"used":true},{"PRN":4,"el":15,"az":199,"ss":35,"used":true},{"PRN":2,"el":34,"az":241,"ss":42,"used":true},{"PRN":27,"el":71,"az":76,"ss":42,"used":true}]}
25 | $GPZDA,143446.28,09,06,2005,00,00*64
26 | $GPGGA,143446,4629.8984,N,00734.0440,E,1,05,3.20,1343.08,M,48.183,M,,*79
27 | $GPRMC,143446,A,4629.8984,N,00734.0440,E,0.1776,10.379,090605,,*18
28 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,3.2,3.4*08
29 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:46.280Z","ept":0.005,"lat":46.498306887,"lon":7.567400477,"alt":1343.079,"epx":24.829,"epy":25.326,"epv":78.615,"track":10.3788,"speed":0.091,"climb":-0.085,"eps":50.65,"epc":157.23}
30 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,66,189,44,29,13,273,00*74
31 | $GPGSV,2,2,08,10,51,304,28,04,15,199,36,02,34,241,42,27,71,076,42*7D
32 | {"class":"SKY","time":"2005-06-09T14:34:47.280Z","xdop":1.66,"ydop":1.69,"vdop":3.42,"tdop":3.05,"hdop":3.20,"gdop":5.15,"pdop":4.16,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":66,"az":189,"ss":44,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":51,"az":304,"ss":28,"used":true},{"PRN":4,"el":15,"az":199,"ss":36,"used":true},{"PRN":2,"el":34,"az":241,"ss":42,"used":true},{"PRN":27,"el":71,"az":76,"ss":42,"used":true}]}
33 | $GPZDA,143447.28,09,06,2005,00,00*65
34 | $GPGGA,143447,4629.8984,N,00734.0440,E,1,05,2.40,1343.08,M,48.183,M,,*7F
35 | $GPRMC,143447,A,4629.8984,N,00734.0440,E,0.1776,10.379,090605,,*19
36 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.4*0F
37 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:47.280Z","ept":0.005,"lat":46.498306887,"lon":7.567400477,"alt":1343.079,"epx":24.829,"epy":25.326,"epv":78.615,"track":10.3788,"speed":0.091,"climb":-0.085,"eps":50.65,"epc":157.23}
38 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,66,189,45,29,13,273,00*75
39 | $GPGSV,2,2,08,10,51,304,28,04,15,199,38,02,34,241,43,27,71,076,42*72
40 | {"class":"SKY","time":"2005-06-09T14:34:48.280Z","xdop":1.66,"ydop":1.69,"vdop":3.42,"tdop":3.05,"hdop":2.40,"gdop":5.15,"pdop":4.16,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":66,"az":189,"ss":45,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":51,"az":304,"ss":28,"used":true},{"PRN":4,"el":15,"az":199,"ss":38,"used":true},{"PRN":2,"el":34,"az":241,"ss":43,"used":true},{"PRN":27,"el":71,"az":76,"ss":42,"used":true}]}
41 | $GPZDA,143448.28,09,06,2005,00,00*6A
42 | $GPGGA,143448,4629.8992,N,00734.0441,E,1,05,2.40,1343.12,M,48.183,M,,*7D
43 | $GPRMC,143448,A,4629.8992,N,00734.0441,E,0.1776,10.379,090605,,*10
44 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.4*0F
45 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:48.280Z","ept":0.005,"lat":46.498319545,"lon":7.567402192,"alt":1343.122,"epx":24.829,"epy":25.326,"epv":78.615,"track":10.3788,"speed":0.091,"climb":-0.085,"eps":50.65,"epc":157.23}
46 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,66,189,45,29,13,273,00*75
47 | $GPGSV,2,2,08,10,51,304,29,04,15,199,37,02,34,241,42,27,71,076,42*7D
48 | {"class":"SKY","time":"2005-06-09T14:34:49.280Z","xdop":1.66,"ydop":1.69,"vdop":3.42,"tdop":3.05,"hdop":2.40,"gdop":5.15,"pdop":4.16,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":66,"az":189,"ss":45,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":51,"az":304,"ss":29,"used":true},{"PRN":4,"el":15,"az":199,"ss":37,"used":true},{"PRN":2,"el":34,"az":241,"ss":42,"used":true},{"PRN":27,"el":71,"az":76,"ss":42,"used":true}]}
49 | $GPZDA,143449.28,09,06,2005,00,00*6B
50 | $GPGGA,143449,4629.8992,N,00734.0441,E,1,05,2.40,1343.12,M,48.183,M,,*7C
51 | $GPRMC,143449,A,4629.8992,N,00734.0441,E,0.1776,10.379,090605,,*11
52 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.4*0F
53 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:49.280Z","ept":0.005,"lat":46.498319545,"lon":7.567402192,"alt":1343.122,"epx":24.829,"epy":25.326,"epv":78.615,"track":10.3788,"speed":0.091,"climb":-0.085,"eps":50.65,"epc":157.23}
54 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,66,189,45,29,13,273,00*75
55 | $GPGSV,2,2,08,10,51,304,32,04,15,199,36,02,34,241,43,27,71,076,42*77
56 | {"class":"SKY","time":"2005-06-09T14:34:50.280Z","xdop":1.66,"ydop":1.69,"vdop":3.42,"tdop":3.05,"hdop":2.40,"gdop":5.15,"pdop":4.16,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":66,"az":189,"ss":45,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":51,"az":304,"ss":32,"used":true},{"PRN":4,"el":15,"az":199,"ss":36,"used":true},{"PRN":2,"el":34,"az":241,"ss":43,"used":true},{"PRN":27,"el":71,"az":76,"ss":42,"used":true}]}
57 | $GPZDA,143450.28,09,06,2005,00,00*63
58 | $GPGGA,143450,4629.8992,N,00734.0441,E,1,05,2.40,1343.12,M,48.183,M,,*74
59 | $GPRMC,143450,A,4629.8992,N,00734.0441,E,0.1776,10.379,090605,,*19
60 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.4*0F
61 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:50.280Z","ept":0.005,"lat":46.498319545,"lon":7.567402192,"alt":1343.122,"epx":24.829,"epy":25.326,"epv":78.615,"track":10.3788,"speed":0.091,"climb":-0.085,"eps":50.65,"epc":157.23}
62 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,66,189,45,29,13,273,00*75
63 | $GPGSV,2,2,08,10,51,304,29,04,15,199,36,02,34,241,41,27,71,076,42*7F
64 | {"class":"SKY","time":"2005-06-09T14:34:51.280Z","xdop":1.66,"ydop":1.69,"vdop":3.42,"tdop":3.05,"hdop":2.40,"gdop":5.15,"pdop":4.16,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":66,"az":189,"ss":45,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":51,"az":304,"ss":29,"used":true},{"PRN":4,"el":15,"az":199,"ss":36,"used":true},{"PRN":2,"el":34,"az":241,"ss":41,"used":true},{"PRN":27,"el":71,"az":76,"ss":42,"used":true}]}
65 | $GPZDA,143451.28,09,06,2005,00,00*62
66 | $GPGGA,143451,4629.8999,N,00734.0442,E,1,05,2.40,1343.17,M,48.183,M,,*78
67 | $GPRMC,143451,A,4629.8999,N,00734.0442,E,0.1776,10.379,090605,,*10
68 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.4*0F
69 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:51.280Z","ept":0.005,"lat":46.498332203,"lon":7.567403907,"alt":1343.165,"epx":24.829,"epy":25.326,"epv":78.615,"track":10.3788,"speed":0.091,"climb":-0.085,"eps":50.65,"epc":157.23}
70 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,66,189,45,29,13,273,00*75
71 | $GPGSV,2,2,08,10,51,304,25,04,15,199,36,02,34,241,42,27,71,076,42*70
72 | {"class":"SKY","time":"2005-06-09T14:34:52.280Z","xdop":1.66,"ydop":1.69,"vdop":3.42,"tdop":3.05,"hdop":2.40,"gdop":5.15,"pdop":4.16,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":66,"az":189,"ss":45,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":51,"az":304,"ss":25,"used":true},{"PRN":4,"el":15,"az":199,"ss":36,"used":true},{"PRN":2,"el":34,"az":241,"ss":42,"used":true},{"PRN":27,"el":71,"az":76,"ss":42,"used":true}]}
73 | $GPZDA,143452.28,09,06,2005,00,00*61
74 | $GPGGA,143452,4629.8999,N,00734.0442,E,1,05,3.20,1343.17,M,48.183,M,,*7C
75 | $GPRMC,143452,A,4629.8999,N,00734.0442,E,0.1776,10.379,090605,,*13
76 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,3.2,3.4*08
77 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:52.280Z","ept":0.005,"lat":46.498332203,"lon":7.567403907,"alt":1343.165,"epx":24.829,"epy":25.326,"epv":78.615,"track":10.3788,"speed":0.091,"climb":-0.085,"eps":50.65,"epc":157.23}
78 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,66,189,46,29,13,273,00*76
79 | $GPGSV,2,2,08,10,51,304,32,04,15,199,36,02,34,241,42,27,71,076,42*76
80 | {"class":"SKY","time":"2005-06-09T14:34:53.280Z","xdop":1.66,"ydop":1.69,"vdop":3.42,"tdop":3.05,"hdop":3.20,"gdop":5.15,"pdop":4.16,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":66,"az":189,"ss":46,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":51,"az":304,"ss":32,"used":true},{"PRN":4,"el":15,"az":199,"ss":36,"used":true},{"PRN":2,"el":34,"az":241,"ss":42,"used":true},{"PRN":27,"el":71,"az":76,"ss":42,"used":true}]}
81 | $GPZDA,143453.28,09,06,2005,00,00*60
82 | $GPGGA,143453,4629.9000,N,00734.0435,E,1,05,2.40,1343.07,M,48.183,M,,*73
83 | $GPRMC,143453,A,4629.9000,N,00734.0435,E,0.1776,10.379,090605,,*1A
84 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.4*0F
85 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:53.280Z","ept":0.005,"lat":46.498333062,"lon":7.567390997,"alt":1343.075,"epx":24.829,"epy":25.326,"epv":78.615,"track":10.3787,"speed":0.091,"climb":-0.085,"eps":50.65,"epc":157.23}
86 | $GPGSV,2,1,08,23,06,084,00,28,07,160,00,08,66,189,46,29,13,273,00*76
87 | $GPGSV,2,2,08,10,51,304,31,04,15,199,37,02,34,241,42,27,71,076,43*75
88 | {"class":"SKY","time":"2005-06-09T14:34:54.280Z","xdop":1.66,"ydop":1.69,"vdop":3.42,"tdop":3.05,"hdop":2.40,"gdop":5.15,"pdop":4.16,"satellites":[{"PRN":23,"el":6,"az":84,"ss":0,"used":false},{"PRN":28,"el":7,"az":160,"ss":0,"used":false},{"PRN":8,"el":66,"az":189,"ss":46,"used":true},{"PRN":29,"el":13,"az":273,"ss":0,"used":false},{"PRN":10,"el":51,"az":304,"ss":31,"used":true},{"PRN":4,"el":15,"az":199,"ss":37,"used":true},{"PRN":2,"el":34,"az":241,"ss":42,"used":true},{"PRN":27,"el":71,"az":76,"ss":43,"used":true}]}
89 | $GPZDA,143454.28,09,06,2005,00,00*67
90 | $GPGGA,143454,4629.9004,N,00734.0436,E,1,05,2.40,1342.39,M,48.183,M,,*7F
91 | $GPRMC,143454,A,4629.9004,N,00734.0436,E,0.1776,10.379,090605,,*1A
92 | $GPGSA,A,3,8,10,4,2,27,,,,,,,,4.2,2.4,3.4*0F
93 | {"class":"TPV","mode":3,"time":"2005-06-09T14:34:54.280Z","ept":0.005,"lat":46.498339529,"lon":7.567392712,"alt":1342.392,"epx":24.829,"epy":25.326,"epv":78.615,"track":10.3787,"speed":0.091,"climb":-0.085,"eps":50.65,"epc":157.23}
94 |
--------------------------------------------------------------------------------
/pizero_gpslog/gpsd.py:
--------------------------------------------------------------------------------
1 | """
2 | This file copied from `gpsd-py3 `_
3 | by Martijn Braam, as of 41543d2 on October 14, 2017 (version 0.3.0).
4 |
5 | Licensed under the MIT license, per setup.py in the above repo.
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy of
8 | this software and associated documentation files (the "Software"), to deal in
9 | the Software without restriction, including without limitation the rights to
10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11 | the Software, and to permit persons to whom the Software is furnished to do so,
12 | subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 | """
24 |
25 | import socket
26 | import json
27 | import logging
28 | import datetime
29 |
30 | gpsTimeFormat = '%Y-%m-%dT%H:%M:%S.%fZ'
31 |
32 | logger = logging.getLogger(__name__)
33 |
34 |
35 | class NoFixError(Exception):
36 | pass
37 |
38 |
39 | class NoActiveGpsError(Exception):
40 | pass
41 |
42 |
43 | class GpsResponse(object):
44 | """ Class representing geo information returned by GPSD
45 |
46 | Use the attributes to get the raw gpsd data, use the methods to get parsed
47 | and corrected information.
48 |
49 | :type mode: int
50 | :type sats: int
51 | :type sats_valid: int
52 | :type lon: float
53 | :type lat: float
54 | :type alt: float
55 | :type track: float
56 | :type hspeed: float
57 | :type climb: float
58 | :type time: str
59 | :type error: dict[str, float]
60 |
61 | :var self.mode: Indicates the status of the GPS reception,
62 | 0=No value, 1=No fix, 2=2D fix, 3=3D fix
63 | :var self.sats: The number of satellites received by the GPS unit
64 | :var self.sats_valid: The number of satellites with valid information
65 | :var self.lon: Longitude in degrees
66 | :var self.lat: Latitude in degrees
67 | :var self.alt: Altitude in meters
68 | :var self.track: Course over ground, degrees from true north
69 | :var self.hspeed: Speed over ground, meters per second
70 | :var self.climb: Climb (positive) or sink (negative) rate, meters per second
71 | :var self.time: Time/date stamp in ISO8601 format, UTC. May have a
72 | fractional part of up to .001sec precision.
73 | :var self.error: GPSD error margin information
74 |
75 | GPSD error margin information
76 | -----------------------------
77 |
78 | c: ecp: Climb/sink error estimate in meters/sec, 95% confidence.
79 | s: eps: Speed error estinmate in meters/sec, 95% confidence.
80 | t: ept: Estimated timestamp error (%f, seconds, 95% confidence).
81 | v: epv: Estimated vertical error in meters, 95% confidence. Present if mode
82 | is 3 and DOPs can be calculated from the satellite view.
83 | x: epx: Longitude error estimate in meters, 95% confidence. Present if mode
84 | is 2 or 3 and DOPs can be calculated from the satellite view.
85 | y: epy: Latitude error estimate in meters, 95% confidence. Present if mode
86 | is 2 or 3 and DOPs can be calculated from the satellite view.
87 | """
88 |
89 | def __init__(self):
90 | self.mode = 0
91 | self.sats = 0
92 | self.sats_valid = 0
93 | self.lon = 0.0
94 | self.lat = 0.0
95 | self.alt = 0.0
96 | self.track = 0
97 | self.hspeed = 0
98 | self.climb = 0
99 | self.time = ''
100 | self.error = {}
101 | self._raw_response = {}
102 |
103 | @classmethod
104 | def from_json(cls, packet):
105 | """ Create GpsResponse instance based on the json data from GPSD
106 | :type packet: dict
107 | :param packet: JSON decoded GPSD response
108 | :return: GpsResponse
109 | """
110 | result = cls()
111 | result._raw_response = packet
112 | if not packet['active']:
113 | raise NoActiveGpsError("No active GPS.")
114 | last_tpv = packet['tpv'][-1]
115 | last_sky = packet['sky'][-1]
116 |
117 | if 'satellites' in last_sky:
118 | result.sats = len(last_sky['satellites'])
119 | result.sats_valid = len(
120 | [sat for sat in last_sky['satellites'] if sat['used'] is True])
121 | else:
122 | result.sats = 0
123 | result.sats_valid = 0
124 |
125 | result.mode = last_tpv['mode']
126 |
127 | if last_tpv['mode'] >= 2:
128 | result.lon = last_tpv['lon'] if 'lon' in last_tpv else 0.0
129 | result.lat = last_tpv['lat'] if 'lat' in last_tpv else 0.0
130 | result.track = last_tpv['track'] if 'track' in last_tpv else 0
131 | result.hspeed = last_tpv['speed'] if 'speed' in last_tpv else 0
132 | result.time = last_tpv['time'] if 'time' in last_tpv else ''
133 | result.error = {
134 | 'c': 0,
135 | 's': last_tpv['eps'] if 'eps' in last_tpv else 0,
136 | 't': last_tpv['ept'] if 'ept' in last_tpv else 0,
137 | 'v': 0,
138 | 'x': last_tpv['epx'] if 'epx' in last_tpv else 0,
139 | 'y': last_tpv['epy'] if 'epy' in last_tpv else 0
140 | }
141 |
142 | if last_tpv['mode'] >= 3:
143 | result.alt = last_tpv['alt'] if 'alt' in last_tpv else 0.0
144 | result.climb = last_tpv['climb'] if 'climb' in last_tpv else 0
145 | result.error['c'] = last_tpv['epc'] if 'epc' in last_tpv else 0
146 | result.error['v'] = last_tpv['epv'] if 'epv' in last_tpv else 0
147 |
148 | return result
149 |
150 | def position(self):
151 | """ Get the latitude and longtitude as tuple.
152 | Needs at least 2D fix.
153 |
154 | :return: (float, float)
155 | """
156 | if self.mode < 2:
157 | raise NoFixError("Needs at least 2D fix")
158 | return self.lat, self.lon
159 |
160 | def altitude(self):
161 | """ Get the altitude in meters.
162 | Needs 3D fix
163 |
164 | :return: (float)
165 | """
166 | if self.mode < 3:
167 | raise NoFixError("Needs at least 3D fix")
168 | return self.alt
169 |
170 | def movement(self):
171 | """ Get the speed and direction of the current movement as dict
172 |
173 | The speed is the horizontal speed.
174 | The climb is the vertical speed
175 | The track is te direction of the motion
176 | Needs at least 3D fix
177 |
178 | :return: dict[str, float]
179 | """
180 | if self.mode < 3:
181 | raise NoFixError("Needs at least 3D fix")
182 | return {"speed": self.hspeed, "track": self.track, "climb": self.climb}
183 |
184 | def speed_vertical(self):
185 | """ Get the vertical speed with the small movements filtered out.
186 | Needs at least 2D fix
187 |
188 | :return: float
189 | """
190 | if self.mode < 2:
191 | raise NoFixError("Needs at least 2D fix")
192 | if abs(self.climb) < self.error['c']:
193 | return 0
194 | else:
195 | return self.climb
196 |
197 | def speed(self):
198 | """ Get the horizontal speed with the small movements filtered out.
199 | Needs at least 2D fix
200 |
201 | :return: float
202 | """
203 | if self.mode < 2:
204 | raise NoFixError("Needs at least 2D fix")
205 | if self.hspeed < self.error['s']:
206 | return 0
207 | else:
208 | return self.hspeed
209 |
210 | def position_precision(self):
211 | """ Get the error margin in meters for the current fix.
212 |
213 | The first value return is the horizontal error, the second
214 | is the vertical error if a 3D fix is available
215 |
216 | Needs at least 2D fix
217 |
218 | :return: (float, float)
219 | """
220 | if self.mode < 2:
221 | raise NoFixError("Needs at least 2D fix")
222 | return max(self.error['x'], self.error['y']), self.error['v']
223 |
224 | def map_url(self):
225 | """ Get a openstreetmap url for the current position
226 | :return: str
227 | """
228 | if self.mode < 2:
229 | raise NoFixError("Needs at least 2D fix")
230 | return "http://www.openstreetmap.org/?mlat={}&mlon={}&zoom=15".format(
231 | self.lat, self.lon
232 | )
233 |
234 | def get_time(self, local_time=False):
235 | """ Get the GPS time
236 |
237 | :type local_time: bool
238 | :param local_time: Return date in the local timezone instead of UTC
239 | :return: datetime.datetime
240 | """
241 | if self.mode < 2:
242 | raise NoFixError("Needs at least 2D fix")
243 | time = datetime.datetime.strptime(self.time, gpsTimeFormat)
244 |
245 | if local_time:
246 | time = time.replace(tzinfo=datetime.timezone.utc).astimezone()
247 |
248 | return time
249 |
250 | @property
251 | def raw_packet(self):
252 | """
253 | Return the deserialized gpsd response, unaltered.
254 |
255 | :return: gpsd response, deserialized from JSON
256 | :rtype: dict
257 | """
258 | return self._raw_response
259 |
260 | def __repr__(self):
261 | modes = {
262 | 0: 'No mode',
263 | 1: 'No fix',
264 | 2: '2D fix',
265 | 3: '3D fix'
266 | }
267 | if self.mode < 2:
268 | return "".format(modes[self.mode])
269 | if self.mode == 2:
270 | return "".format(self.lat, self.lon)
271 | if self.mode == 3:
272 | return "".format(
273 | self.lat, self.lon, self.alt
274 | )
275 |
276 |
277 | class GpsClient(object):
278 |
279 | def __init__(self, host="127.0.0.1", port=2947):
280 | """ Connect to a GPSD instance
281 | :param host: hostname for the GPSD server
282 | :param port: port for the GPSD server
283 | """
284 | self._state = {}
285 | logger.debug("Connecting to gpsd socket at {}:{}".format(host, port))
286 | self._gpsd_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
287 | self._gpsd_socket.connect((host, port))
288 | self._gpsd_stream = self._gpsd_socket.makefile(mode="rw")
289 | logger.debug("Waiting for welcome message")
290 | welcome_raw = self._gpsd_stream.readline()
291 | welcome = json.loads(welcome_raw)
292 | if welcome['class'] != "VERSION":
293 | raise Exception(
294 | "Unexpected data received as welcome. Is the server a gpsd 3 "
295 | "server? (Data: %s)" % welcome_raw
296 | )
297 | logger.debug("Enabling gps")
298 | self._gpsd_stream.write('?WATCH={"enable":true}\n')
299 | self._gpsd_stream.flush()
300 |
301 | for i in range(0, 2):
302 | raw = self._gpsd_stream.readline()
303 | parsed = json.loads(raw)
304 | self._parse_state_packet(parsed)
305 |
306 | def _parse_state_packet(self, json_data):
307 | if json_data['class'] == 'DEVICES':
308 | if not json_data['devices']:
309 | logger.warning('No gps devices found')
310 | self._state['devices'] = json_data
311 | elif json_data['class'] == 'WATCH':
312 | self._state['watch'] = json_data
313 | else:
314 | raise Exception(
315 | "Unexpected message received from gps: {}".format(
316 | json_data['class']
317 | )
318 | )
319 |
320 | @property
321 | def current_fix(self):
322 | """ Poll gpsd for a new position
323 | :return: GpsResponse
324 | """
325 | logger.debug("Polling gps")
326 | self._gpsd_stream.write("?POLL;\n")
327 | self._gpsd_stream.flush()
328 | raw = self._gpsd_stream.readline()
329 | response = json.loads(raw)
330 | if response['class'] != 'POLL':
331 | raise Exception(
332 | "Unexpected message received from gps: {}".format(
333 | response['class']
334 | )
335 | )
336 | return GpsResponse.from_json(response)
337 |
338 | @property
339 | def device(self):
340 | """ Get information about current gps device
341 | :return: dict
342 | """
343 | return {
344 | 'path': self._state['devices']['devices'][0]['path'],
345 | 'speed': self._state['devices']['devices'][0]['bps'],
346 | 'driver': self._state['devices']['devices'][0]['driver']
347 | }
348 |
--------------------------------------------------------------------------------
/pizero_gpslog/displays/epd2in13bc.py:
--------------------------------------------------------------------------------
1 | # *****************************************************************************
2 | # * | File : epd2in13bc.py
3 | # * | Author : Waveshare team
4 | # * | Function : Electronic paper driver
5 | # * | Info :
6 | # *----------------
7 | # * | This version: V4.0
8 | # * | Date : 2019-06-20
9 | # # | Info : python demo
10 | # -----------------------------------------------------------------------------
11 | # Permission is hereby granted, free of charge, to any person obtaining a copy
12 | # of this software and associated documnetation files (the "Software"), to deal
13 | # in the Software without restriction, including without limitation the rights
14 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 | # copies of the Software, and to permit persons to whom the Software is
16 | # furished to do so, subject to the following conditions:
17 | #
18 | # The above copyright notice and this permission notice shall be included in
19 | # all copies or substantial portions of the Software.
20 | #
21 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | # FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | # LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | # THE SOFTWARE.
28 | #
29 | """
30 | Modified from:
31 | https://github.com/waveshare/e-Paper/blob/
32 | 717cbb8d9215e58f9f3cdde45ee329f516504afe/RaspberryPi%26JetsonNano/python/
33 | lib/waveshare_epd/epd2in13bc.py
34 |
35 | Display driver class for Waveshare e-Paper Display HAT 2.13 inch (B)
36 |
37 | https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT_(B)
38 | https://www.amazon.com/gp/product/B075FR81WL/
39 |
40 | The latest version of this package is available at:
41 |
42 |
43 | ##################################################################################
44 | Copyright 2018-2020 Jason Antman
45 |
46 | This file is part of pizero-gpslog, also known as pizero-gpslog.
47 |
48 | pizero-gpslog is free software: you can redistribute it and/or modify
49 | it under the terms of the GNU Affero General Public License as published by
50 | the Free Software Foundation, either version 3 of the License, or
51 | (at your option) any later version.
52 |
53 | pizero-gpslog is distributed in the hope that it will be useful,
54 | but WITHOUT ANY WARRANTY; without even the implied warranty of
55 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
56 | GNU Affero General Public License for more details.
57 |
58 | You should have received a copy of the GNU Affero General Public License
59 | along with pizero-gpslog. If not, see .
60 |
61 | The Copyright and Authors attributions contained herein may not be removed or
62 | otherwise altered, except to add the Author attribution of a contributor to
63 | this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
64 | ##################################################################################
65 | While not legally required, I sincerely request that anyone who finds
66 | bugs please submit them at or
67 | to me via email, and that you send any contributions or improvements
68 | either as a pull request on GitHub, or to me via email.
69 | ##################################################################################
70 |
71 | AUTHORS:
72 | Jason Antman
73 | ##################################################################################
74 | """
75 |
76 | import time
77 | import logging
78 | import spidev
79 | import RPi.GPIO
80 | from typing import Optional, ClassVar, Tuple
81 | from pizero_gpslog.displays.base import BaseDisplay
82 | from pizero_gpslog.utils import FixType
83 | from datetime import datetime
84 | from PIL import Image, ImageDraw
85 |
86 |
87 | logger = logging.getLogger(__name__)
88 |
89 |
90 | class EPD2in13bc(BaseDisplay):
91 |
92 | #: width of the display in characters
93 | width_chars: ClassVar[int] = 21
94 |
95 | #: height of the display in lines
96 | height_lines: ClassVar[int] = 5
97 |
98 | #: the minimum number of seconds between refreshes of the display
99 | min_refresh_seconds: ClassVar[int] = 15
100 |
101 | def __init__(
102 | self, bus: int = 0, device: int = 0, rst_pin: int = 17,
103 | dc_pin: int = 25, cs_pin: int = 8, busy_pin: int = 24,
104 | epd_width: int = 104, epd_height: int = 212
105 | ):
106 | super().__init__()
107 | logger.debug(
108 | 'EPD.__init__(bus=%d, device=%d, rst_pin=%d, dc_pin=%d,'
109 | 'cs_pin=%d, busy_pin=%d, epd_width=%d, epd_height=%d)',
110 | bus, device, rst_pin, dc_pin, cs_pin, busy_pin, epd_width,
111 | epd_height
112 | )
113 | self._GPIO = RPi.GPIO
114 | self._SPI = spidev.SpiDev(bus, device)
115 | self._reset_pin: int = rst_pin
116 | self._dc_pin: int = dc_pin
117 | self._busy_pin: int = busy_pin
118 | self._cs_pin: int = cs_pin
119 | self._width: int = epd_width
120 | self._height: int = epd_height
121 | self._GPIO.setmode(self._GPIO.BCM)
122 | self._GPIO.setwarnings(False)
123 | self._GPIO.setup(self._reset_pin, self._GPIO.OUT)
124 | self._GPIO.setup(self._dc_pin, self._GPIO.OUT)
125 | self._GPIO.setup(self._cs_pin, self._GPIO.OUT)
126 | self._GPIO.setup(self._busy_pin, self._GPIO.IN)
127 | self._SPI.max_speed_hz = 4000000
128 | self._SPI.mode = 0b00
129 | self._initialize()
130 | self._wrote_black: bool = True
131 | self._wrote_red: bool = True
132 | self.clear()
133 | self._wrote_black: bool = False
134 | self._wrote_red: bool = False
135 | time.sleep(1) # present in upstream example
136 | logger.debug('EPD initialize complete')
137 |
138 | def _digital_write(self, pin, value):
139 | self._GPIO.output(pin, value)
140 |
141 | def _digital_read(self, pin):
142 | return self._GPIO.input(pin)
143 |
144 | def _delay_ms(self, delaytime):
145 | time.sleep(delaytime / 1000.0)
146 |
147 | def _spi_writebyte(self, data):
148 | self._SPI.writebytes(data)
149 |
150 | def _hardware_reset(self):
151 | logger.debug('Reset EPD')
152 | self._digital_write(self._reset_pin, 1)
153 | self._delay_ms(200)
154 | self._digital_write(self._reset_pin, 0)
155 | self._delay_ms(10)
156 | self._digital_write(self._reset_pin, 1)
157 | self._delay_ms(200)
158 |
159 | def _send_command(self, command):
160 | self._digital_write(self._dc_pin, 0)
161 | self._digital_write(self._cs_pin, 0)
162 | self._spi_writebyte([command])
163 | self._digital_write(self._cs_pin, 1)
164 |
165 | def _send_data(self, data):
166 | self._digital_write(self._dc_pin, 1)
167 | self._digital_write(self._cs_pin, 0)
168 | self._spi_writebyte([data])
169 | self._digital_write(self._cs_pin, 1)
170 |
171 | @property
172 | def _is_busy(self):
173 | return self._digital_read(self._busy_pin) == 0
174 |
175 | def _wait_for_not_busy(self):
176 | logger.debug("Waiting until display is not busy (100ms check interval)")
177 | while self._is_busy:
178 | self._delay_ms(100)
179 | logger.debug("display is no longer busy")
180 |
181 | def _initialize(self):
182 | logger.debug('Initialize EPD')
183 | self._hardware_reset()
184 | self._send_command(0x06) # BOOSTER_SOFT_START
185 | self._send_data(0x17)
186 | self._send_data(0x17)
187 | self._send_data(0x17)
188 | self._send_command(0x04) # POWER_ON
189 | self._wait_for_not_busy()
190 | self._send_command(0x00) # PANEL_SETTING
191 | self._send_data(0x8F)
192 | self._send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
193 | self._send_data(0xF0)
194 | self._send_command(0x61) # RESOLUTION_SETTING
195 | self._send_data(self._width & 0xff)
196 | self._send_data(self._height >> 8)
197 | self._send_data(self._height & 0xff)
198 | logger.debug('EPD Initialized')
199 |
200 | def _getbuffer(self, image):
201 | buf = [0xFF] * (int(self._width/8) * self._height)
202 | image_monocolor = image.convert('1')
203 | imwidth, imheight = image_monocolor.size
204 | pixels = image_monocolor.load()
205 | if imwidth == self._width and imheight == self._height:
206 | logger.debug("Vertical")
207 | for y in range(imheight):
208 | for x in range(imwidth):
209 | # Set the bits for the column of pixels at the current position.
210 | if pixels[x, y] == 0:
211 | buf[int((x + y * self._width) / 8)] &= ~(0x80 >> (x % 8))
212 | elif imwidth == self._height and imheight == self._width:
213 | logger.debug("Horizontal")
214 | for y in range(imheight):
215 | for x in range(imwidth):
216 | newx = y
217 | newy = self._height - x - 1
218 | if pixels[x, y] == 0:
219 | buf[int((newx + newy*self._width) / 8)] &= ~(0x80 >> (y % 8))
220 | return buf
221 |
222 | def _display(
223 | self, black: Optional[Image.Image] = None,
224 | red: Optional[Image.Image] = None
225 | ):
226 | if black is not None:
227 | logger.debug('Displaying black image')
228 | buf = self._getbuffer(black)
229 | self._send_command(0x10)
230 | for i in range(0, int(self._width * self._height / 8)):
231 | self._send_data(buf[i])
232 | self._send_command(0x92)
233 | self._wrote_black = True
234 | if red is not None:
235 | logger.debug('Displaying red image')
236 | buf = self._getbuffer(red)
237 | self._send_command(0x13)
238 | for i in range(0, int(self._width * self._height / 8)):
239 | self._send_data(buf[i])
240 | self._send_command(0x92)
241 | self._wrote_red = True
242 | logger.debug('Refresh')
243 | self._send_command(0x12) # REFRESH
244 | self._wait_for_not_busy()
245 | logger.debug('Done refreshing')
246 |
247 | def update_display(
248 | self, fix_type: FixType, lat: float, lon: float, extradata: str,
249 | fix_precision: Tuple[float, float], dt: datetime, should_clear: bool
250 | ):
251 | if should_clear:
252 | self.clear()
253 | lines = [dt.strftime('%H:%M:%S UTC')]
254 | if fix_type == FixType.NO_GPS:
255 | lines.extend(['No GPS yet', '', ''])
256 | elif fix_type == FixType.NO_FIX:
257 | lines.extend(['No Fix yet', '', ''])
258 | else:
259 | ft = '??'
260 | if fix_type == FixType.FIX_2D:
261 | ft = '2D'
262 | elif fix_type == FixType.FIX_3D:
263 | ft = '3D'
264 | lines.append(f'{ft} {fix_precision[0]:.8},{fix_precision[1]:.8}')
265 | lines.append(f'Lat: {lat:.15}')
266 | lines.append(f'Lon: {lon:.15}')
267 | lines.append(extradata)
268 | self._write_lines(lines)
269 |
270 | def _write_lines(self, lines):
271 | """
272 | Write ``lines`` to the display.
273 | """
274 | logging.info('Begin update display')
275 | font = self.font(16)
276 | HBlackimage = Image.new('1', (self._height, self._width), 255)
277 | drawblack = ImageDraw.Draw(HBlackimage)
278 | for idx, content in enumerate(lines):
279 | drawblack.text(
280 | (0, 20 * idx), content, font=font, fill=0
281 | )
282 | self._display(black=HBlackimage)
283 | logging.info('End update display')
284 |
285 | def clear(self):
286 | if self._wrote_black:
287 | logger.debug('Clearing black')
288 | self._send_command(0x10)
289 | for i in range(0, int(self._width * self._height / 8)):
290 | self._send_data(0xFF)
291 | self._send_command(0x92)
292 | self._wrote_black = False
293 | if self._wrote_red:
294 | logger.debug('Clearing red')
295 | self._send_command(0x13)
296 | for i in range(0, int(self._width * self._height / 8)):
297 | self._send_data(0xFF)
298 | self._send_command(0x92)
299 | self._wrote_red = False
300 | logger.debug('Done clearning')
301 | logger.debug('Refresh')
302 | self._send_command(0x12) # REFRESH
303 | self._wait_for_not_busy()
304 | logger.debug('Done refreshing')
305 |
306 | def _put_to_sleep(self):
307 | self._send_command(0x02) # POWER_OFF
308 | self._wait_for_not_busy()
309 | self._send_command(0x07) # DEEP_SLEEP
310 | self._send_data(0xA5) # check code
311 |
312 | def _destroy(self):
313 | logger.debug("spi end")
314 | self._SPI.close()
315 | logger.debug("close 5V, Module enters 0 power consumption ...")
316 | self._GPIO.output(self._reset_pin, 0)
317 | self._GPIO.output(self._dc_pin, 0)
318 | self._GPIO.cleanup()
319 | logger.debug('EPD cleaned up')
320 |
321 | def __del__(self):
322 | self._put_to_sleep()
323 | self._destroy()
324 |
--------------------------------------------------------------------------------
/pizero_gpslog/tests/data/gpsd/bu353s4.log.chk:
--------------------------------------------------------------------------------
1 | $GPGGA,030719.000,3747.0873,S,17518.8938,E,1,08,1.1,59.9,M,23.7,M,,0000*7D
2 | {"class":"TPV","mode":3,"lat":-37.784788333,"lon":175.314896667,"alt":59.900}
3 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
4 | {"class":"TPV","mode":3,"lat":-37.784788333,"lon":175.314896667,"alt":59.900,"epv":34.500}
5 | $GPRMC,030719.000,A,3747.0873,S,17518.8938,E,0.69,181.39,311214,,,A*7D
6 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:19.000Z","ept":0.005,"lat":-37.784788333,"lon":175.314896667,"alt":59.900,"epv":34.500,"track":181.3900,"speed":0.355}
7 | $GPGGA,030720.000,3747.0875,S,17518.8938,E,1,08,1.1,59.8,M,23.7,M,,0000*70
8 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
9 | $GPRMC,030720.000,A,3747.0875,S,17518.8938,E,1.15,181.39,311214,,,A*7B
10 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:20.000Z","ept":0.005,"lat":-37.784791667,"lon":175.314896667,"alt":59.800,"epv":34.500,"track":181.3900,"speed":0.592,"climb":-0.100,"epc":69.00}
11 | $GPGGA,030721.000,3747.0876,S,17518.8934,E,1,08,1.1,60.1,M,23.7,M,,0000*7D
12 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
13 | $GPGSV,3,1,12,17,72,207,32,28,59,095,31,06,35,347,25,01,29,132,23*7B
14 | $GPGSV,3,2,12,30,23,011,29,20,17,069,20,26,09,293,23,13,07,304,10*7C
15 | $GPGSV,3,3,12,11,12,132,04,08,38,008,,02,35,348,,03,14,091,*73
16 | {"class":"SKY","xdop":0.63,"ydop":0.85,"vdop":1.50,"tdop":0.89,"hdop":1.10,"gdop":2.01,"pdop":1.80,"satellites":[{"PRN":17,"el":72,"az":207,"ss":32,"used":true},{"PRN":28,"el":59,"az":95,"ss":31,"used":true},{"PRN":6,"el":35,"az":347,"ss":25,"used":true},{"PRN":1,"el":29,"az":132,"ss":23,"used":true},{"PRN":30,"el":23,"az":11,"ss":29,"used":true},{"PRN":20,"el":17,"az":69,"ss":20,"used":true},{"PRN":26,"el":9,"az":293,"ss":23,"used":true},{"PRN":13,"el":7,"az":304,"ss":10,"used":true},{"PRN":11,"el":12,"az":132,"ss":4,"used":false},{"PRN":8,"el":38,"az":8,"ss":0,"used":false},{"PRN":2,"el":35,"az":348,"ss":0,"used":false},{"PRN":3,"el":14,"az":91,"ss":0,"used":false}]}
17 | $GPRMC,030721.000,A,3747.0876,S,17518.8934,E,0.89,181.39,311214,,,A*71
18 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:21.000Z","ept":0.005,"lat":-37.784793333,"lon":175.314890000,"alt":60.100,"epx":9.484,"epy":12.791,"epv":34.500,"track":181.3900,"speed":0.458,"climb":0.300,"epc":69.00}
19 | $GPGGA,030722.000,3747.0877,S,17518.8933,E,1,08,1.1,60.1,M,23.7,M,,0000*78
20 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
21 | $GPRMC,030722.000,A,3747.0877,S,17518.8933,E,0.12,181.39,311214,,,A*76
22 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:22.000Z","ept":0.005,"lat":-37.784795000,"lon":175.314888333,"alt":60.100,"epx":9.484,"epy":12.791,"epv":34.500,"track":181.3900,"speed":0.062,"climb":0.000,"eps":25.58,"epc":69.00}
23 | $GPGGA,030723.000,3747.0878,S,17518.8935,E,1,08,1.1,60.0,M,23.7,M,,0000*71
24 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
25 | $GPRMC,030723.000,A,3747.0878,S,17518.8935,E,0.66,181.39,311214,,,A*7D
26 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:23.000Z","ept":0.005,"lat":-37.784796667,"lon":175.314891667,"alt":60.000,"epx":9.484,"epy":12.791,"epv":34.500,"track":181.3900,"speed":0.340,"climb":-0.100,"eps":25.58,"epc":69.00}
27 | $GPGGA,030724.000,3747.0878,S,17518.8936,E,1,08,1.1,60.1,M,23.7,M,,0000*74
28 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
29 | $GPRMC,030724.000,A,3747.0878,S,17518.8936,E,0.32,181.39,311214,,,A*78
30 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:24.000Z","ept":0.005,"lat":-37.784796667,"lon":175.314893333,"alt":60.100,"epx":9.484,"epy":12.791,"epv":34.500,"track":181.3900,"speed":0.165,"climb":0.100,"eps":25.58,"epc":69.00}
31 | $GPGGA,030725.000,3747.0878,S,17518.8936,E,1,08,1.1,60.2,M,23.7,M,,0000*76
32 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
33 | $GPRMC,030725.000,A,3747.0878,S,17518.8936,E,0.00,181.39,311214,,,A*78
34 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:25.000Z","ept":0.005,"lat":-37.784796667,"lon":175.314893333,"alt":60.200,"epx":9.484,"epy":12.791,"epv":34.500,"track":181.3900,"speed":0.000,"climb":0.100,"eps":25.58,"epc":69.00}
35 | $GPGGA,030726.000,3747.0877,S,17518.8936,E,1,08,1.1,60.6,M,23.7,M,,0000*7E
36 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
37 | $GPGSV,3,1,12,17,72,207,32,28,59,095,30,06,35,347,25,01,29,132,23*7A
38 | $GPGSV,3,2,12,30,23,011,29,20,17,069,20,26,09,293,23,13,07,304,10*7C
39 | $GPGSV,3,3,12,11,12,132,04,08,38,008,,02,35,348,,03,14,091,*73
40 | {"class":"SKY","xdop":0.63,"ydop":0.85,"vdop":1.50,"tdop":0.89,"hdop":1.10,"gdop":2.01,"pdop":1.80,"satellites":[{"PRN":17,"el":72,"az":207,"ss":32,"used":true},{"PRN":28,"el":59,"az":95,"ss":30,"used":true},{"PRN":6,"el":35,"az":347,"ss":25,"used":true},{"PRN":1,"el":29,"az":132,"ss":23,"used":true},{"PRN":30,"el":23,"az":11,"ss":29,"used":true},{"PRN":20,"el":17,"az":69,"ss":20,"used":true},{"PRN":26,"el":9,"az":293,"ss":23,"used":true},{"PRN":13,"el":7,"az":304,"ss":10,"used":true},{"PRN":11,"el":12,"az":132,"ss":4,"used":false},{"PRN":8,"el":38,"az":8,"ss":0,"used":false},{"PRN":2,"el":35,"az":348,"ss":0,"used":false},{"PRN":3,"el":14,"az":91,"ss":0,"used":false}]}
41 | $GPRMC,030726.000,A,3747.0877,S,17518.8936,E,0.59,181.39,311214,,,A*78
42 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:26.000Z","ept":0.005,"lat":-37.784795000,"lon":175.314893333,"alt":60.600,"epx":9.484,"epy":12.791,"epv":34.500,"track":181.3900,"speed":0.304,"climb":0.400,"eps":25.58,"epc":69.00}
43 | $GPGGA,030727.000,3747.0877,S,17518.8938,E,1,08,1.1,60.8,M,23.7,M,,0000*7F
44 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
45 | $GPRMC,030727.000,A,3747.0877,S,17518.8938,E,0.29,181.39,311214,,,A*70
46 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:27.000Z","ept":0.005,"lat":-37.784795000,"lon":175.314896667,"alt":60.800,"epx":9.484,"epy":12.791,"epv":34.500,"track":181.3900,"speed":0.149,"climb":0.200,"eps":25.58,"epc":69.00}
47 | $GPGGA,030728.000,3747.0878,S,17518.8940,E,1,08,1.1,60.5,M,23.7,M,,0000*7D
48 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
49 | $GPRMC,030728.000,A,3747.0878,S,17518.8940,E,0.52,181.39,311214,,,A*73
50 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:28.000Z","ept":0.005,"lat":-37.784796667,"lon":175.314900000,"alt":60.500,"epx":9.484,"epy":12.791,"epv":34.500,"track":181.3900,"speed":0.268,"climb":-0.300,"eps":25.58,"epc":69.00}
51 | $GPGGA,030729.000,3747.0879,S,17518.8942,E,1,08,1.1,60.7,M,23.7,M,,0000*7D
52 | $GPGSA,A,3,17,28,06,01,30,20,26,13,,,,,1.8,1.1,1.5*33
53 | $GPRMC,030729.000,A,3747.0879,S,17518.8942,E,0.48,181.39,311214,,,A*7A
54 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:29.000Z","ept":0.005,"lat":-37.784798333,"lon":175.314903333,"alt":60.700,"epx":9.484,"epy":12.791,"epv":34.500,"track":181.3900,"speed":0.247,"climb":0.200,"eps":25.58,"epc":69.00}
55 | $GPGGA,030730.000,3747.0878,S,17518.8942,E,1,07,1.1,60.5,M,23.7,M,,0000*79
56 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
57 | $GPRMC,030730.000,A,3747.0878,S,17518.8942,E,0.72,181.39,311214,,,A*7A
58 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:30.000Z","ept":0.005,"lat":-37.784796667,"lon":175.314903333,"alt":60.500,"epx":9.484,"epy":12.791,"epv":34.500,"track":181.3900,"speed":0.370,"climb":-0.200,"eps":25.58,"epc":69.00}
59 | $GPGGA,030731.000,3747.0877,S,17518.8941,E,1,07,1.1,60.5,M,23.7,M,,0000*74
60 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
61 | $GPGSV,3,1,12,17,71,207,32,28,59,095,30,06,36,347,25,01,29,132,23*7A
62 | $GPGSV,3,2,12,30,23,011,29,20,17,070,19,26,08,293,24,13,07,305,07*7F
63 | $GPGSV,3,3,12,08,38,008,,02,35,348,,03,15,091,,24,12,219,*7A
64 | {"class":"SKY","xdop":0.70,"ydop":0.85,"vdop":1.60,"tdop":0.99,"hdop":1.10,"gdop":2.14,"pdop":1.90,"satellites":[{"PRN":17,"el":71,"az":207,"ss":32,"used":true},{"PRN":28,"el":59,"az":95,"ss":30,"used":true},{"PRN":6,"el":36,"az":347,"ss":25,"used":true},{"PRN":1,"el":29,"az":132,"ss":23,"used":true},{"PRN":30,"el":23,"az":11,"ss":29,"used":true},{"PRN":20,"el":17,"az":70,"ss":19,"used":true},{"PRN":26,"el":8,"az":293,"ss":24,"used":true},{"PRN":13,"el":7,"az":305,"ss":7,"used":false},{"PRN":8,"el":38,"az":8,"ss":0,"used":false},{"PRN":2,"el":35,"az":348,"ss":0,"used":false},{"PRN":3,"el":15,"az":91,"ss":0,"used":false},{"PRN":24,"el":12,"az":219,"ss":0,"used":false}]}
65 | $GPRMC,030731.000,A,3747.0877,S,17518.8941,E,0.47,181.39,311214,,,A*71
66 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:31.000Z","ept":0.005,"lat":-37.784795000,"lon":175.314901667,"alt":60.500,"epx":9.484,"epy":12.791,"epv":36.800,"track":181.3900,"speed":0.242,"climb":0.000,"eps":25.58,"epc":71.30}
67 | $GPGGA,030732.000,3747.0876,S,17518.8940,E,1,07,1.1,60.4,M,23.7,M,,0000*76
68 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
69 | $GPRMC,030732.000,A,3747.0876,S,17518.8940,E,0.43,181.39,311214,,,A*76
70 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:32.000Z","ept":0.005,"lat":-37.784793333,"lon":175.314900000,"alt":60.400,"epx":10.467,"epy":12.797,"epv":36.800,"track":181.3900,"speed":0.221,"climb":-0.100,"eps":25.59,"epc":73.60}
71 | $GPGGA,030733.000,3747.0876,S,17518.8941,E,1,07,1.1,60.2,M,23.7,M,,0000*70
72 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
73 | $GPRMC,030733.000,A,3747.0876,S,17518.8941,E,0.23,181.39,311214,,,A*70
74 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:33.000Z","ept":0.005,"lat":-37.784793333,"lon":175.314901667,"alt":60.200,"epx":10.467,"epy":12.797,"epv":36.800,"track":181.3900,"speed":0.118,"climb":-0.200,"eps":25.59,"epc":73.60}
75 | $GPGGA,030734.000,3747.0876,S,17518.8941,E,1,07,1.1,60.0,M,23.7,M,,0000*75
76 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
77 | $GPRMC,030734.000,A,3747.0876,S,17518.8941,E,0.51,181.39,311214,,,A*72
78 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:34.000Z","ept":0.005,"lat":-37.784793333,"lon":175.314901667,"alt":60.000,"epx":10.467,"epy":12.797,"epv":36.800,"track":181.3900,"speed":0.262,"climb":-0.200,"eps":25.59,"epc":73.60}
79 | $GPGGA,030735.000,3747.0876,S,17518.8941,E,1,07,1.1,60.0,M,23.7,M,,0000*74
80 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
81 | $GPRMC,030735.000,A,3747.0876,S,17518.8941,E,0.00,181.39,311214,,,A*77
82 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:35.000Z","ept":0.005,"lat":-37.784793333,"lon":175.314901667,"alt":60.000,"epx":10.467,"epy":12.797,"epv":36.800,"track":181.3900,"speed":0.000,"climb":0.000,"eps":25.59,"epc":73.60}
83 | $GPGGA,030736.000,3747.0876,S,17518.8941,E,1,07,1.1,60.0,M,23.7,M,,0000*77
84 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
85 | $GPGSV,3,1,12,17,71,207,32,28,59,095,30,06,36,347,25,01,29,132,23*7A
86 | $GPGSV,3,2,12,30,23,011,29,20,17,070,19,26,08,293,24,13,07,305,04*7C
87 | $GPGSV,3,3,12,08,38,008,,02,35,348,,03,15,091,,24,12,219,*7A
88 | {"class":"SKY","xdop":0.70,"ydop":0.85,"vdop":1.60,"tdop":0.99,"hdop":1.10,"gdop":2.14,"pdop":1.90,"satellites":[{"PRN":17,"el":71,"az":207,"ss":32,"used":true},{"PRN":28,"el":59,"az":95,"ss":30,"used":true},{"PRN":6,"el":36,"az":347,"ss":25,"used":true},{"PRN":1,"el":29,"az":132,"ss":23,"used":true},{"PRN":30,"el":23,"az":11,"ss":29,"used":true},{"PRN":20,"el":17,"az":70,"ss":19,"used":true},{"PRN":26,"el":8,"az":293,"ss":24,"used":true},{"PRN":13,"el":7,"az":305,"ss":4,"used":false},{"PRN":8,"el":38,"az":8,"ss":0,"used":false},{"PRN":2,"el":35,"az":348,"ss":0,"used":false},{"PRN":3,"el":15,"az":91,"ss":0,"used":false},{"PRN":24,"el":12,"az":219,"ss":0,"used":false}]}
89 | $GPRMC,030736.000,A,3747.0876,S,17518.8941,E,0.00,181.39,311214,,,A*74
90 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:36.000Z","ept":0.005,"lat":-37.784793333,"lon":175.314901667,"alt":60.000,"epx":10.467,"epy":12.797,"epv":36.800,"track":181.3900,"speed":0.000,"climb":0.000,"eps":25.59,"epc":73.60}
91 | $GPGGA,030737.000,3747.0877,S,17518.8942,E,1,07,1.1,60.0,M,23.7,M,,0000*74
92 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
93 | $GPRMC,030737.000,A,3747.0877,S,17518.8942,E,0.25,181.39,311214,,,A*70
94 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:37.000Z","ept":0.005,"lat":-37.784795000,"lon":175.314903333,"alt":60.000,"epx":10.467,"epy":12.797,"epv":36.800,"track":181.3900,"speed":0.129,"climb":0.000,"eps":25.59,"epc":73.60}
95 | $GPGGA,030738.000,3747.0877,S,17518.8942,E,1,07,1.1,60.0,M,23.7,M,,0000*7B
96 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
97 | $GPRMC,030738.000,A,3747.0877,S,17518.8942,E,0.00,181.39,311214,,,A*78
98 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:38.000Z","ept":0.005,"lat":-37.784795000,"lon":175.314903333,"alt":60.000,"epx":10.467,"epy":12.797,"epv":36.800,"track":181.3900,"speed":0.000,"climb":0.000,"eps":25.59,"epc":73.60}
99 | $GPGGA,030739.000,3747.0877,S,17518.8942,E,1,07,1.1,60.0,M,23.7,M,,0000*7A
100 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
101 | $GPRMC,030739.000,A,3747.0877,S,17518.8942,E,0.00,181.39,311214,,,A*79
102 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:39.000Z","ept":0.005,"lat":-37.784795000,"lon":175.314903333,"alt":60.000,"epx":10.467,"epy":12.797,"epv":36.800,"track":181.3900,"speed":0.000,"climb":0.000,"eps":25.59,"epc":73.60}
103 | $GPGGA,030740.000,3747.0877,S,17518.8942,E,1,07,1.1,60.2,M,23.7,M,,0000*76
104 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
105 | $GPRMC,030740.000,A,3747.0877,S,17518.8942,E,0.26,181.39,311214,,,A*73
106 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:40.000Z","ept":0.005,"lat":-37.784795000,"lon":175.314903333,"alt":60.200,"epx":10.467,"epy":12.797,"epv":36.800,"track":181.3900,"speed":0.134,"climb":0.200,"eps":25.59,"epc":73.60}
107 | $GPGGA,030741.000,3747.0877,S,17518.8941,E,1,07,1.1,60.2,M,23.7,M,,0000*74
108 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
109 | $GPGSV,3,1,12,17,71,207,31,28,59,095,30,06,36,347,26,01,29,132,23*7A
110 | $GPGSV,3,2,12,30,23,011,29,20,17,070,18,26,08,293,24,13,07,305,04*7D
111 | $GPGSV,3,3,12,08,38,008,,02,35,348,,03,15,091,,24,12,219,*7A
112 | {"class":"SKY","xdop":0.70,"ydop":0.85,"vdop":1.60,"tdop":0.99,"hdop":1.10,"gdop":2.14,"pdop":1.90,"satellites":[{"PRN":17,"el":71,"az":207,"ss":31,"used":true},{"PRN":28,"el":59,"az":95,"ss":30,"used":true},{"PRN":6,"el":36,"az":347,"ss":26,"used":true},{"PRN":1,"el":29,"az":132,"ss":23,"used":true},{"PRN":30,"el":23,"az":11,"ss":29,"used":true},{"PRN":20,"el":17,"az":70,"ss":18,"used":true},{"PRN":26,"el":8,"az":293,"ss":24,"used":true},{"PRN":13,"el":7,"az":305,"ss":4,"used":false},{"PRN":8,"el":38,"az":8,"ss":0,"used":false},{"PRN":2,"el":35,"az":348,"ss":0,"used":false},{"PRN":3,"el":15,"az":91,"ss":0,"used":false},{"PRN":24,"el":12,"az":219,"ss":0,"used":false}]}
113 | $GPRMC,030741.000,A,3747.0877,S,17518.8941,E,0.00,181.39,311214,,,A*75
114 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:41.000Z","ept":0.005,"lat":-37.784795000,"lon":175.314901667,"alt":60.200,"epx":10.467,"epy":12.797,"epv":36.800,"track":181.3900,"speed":0.000,"climb":0.000,"eps":25.59,"epc":73.60}
115 | $GPGGA,030742.000,3747.0877,S,17518.8941,E,1,07,1.1,60.2,M,23.7,M,,0000*77
116 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
117 | $GPRMC,030742.000,A,3747.0877,S,17518.8941,E,0.00,181.39,311214,,,A*76
118 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:42.000Z","ept":0.005,"lat":-37.784795000,"lon":175.314901667,"alt":60.200,"epx":10.467,"epy":12.797,"epv":36.800,"track":181.3900,"speed":0.000,"climb":0.000,"eps":25.59,"epc":73.60}
119 | $GPGGA,030743.000,3747.0877,S,17518.8941,E,1,07,1.1,60.2,M,23.7,M,,0000*76
120 | $GPGSA,A,3,17,28,06,01,30,20,26,,,,,,1.9,1.1,1.6*33
121 | $GPRMC,030743.000,A,3747.0877,S,17518.8941,E,0.00,181.39,311214,,,A*77
122 | {"class":"TPV","mode":3,"time":"2014-12-31T03:07:43.000Z","ept":0.005,"lat":-37.784795000,"lon":175.314901667,"alt":60.200,"epx":10.467,"epy":12.797,"epv":36.800,"track":181.3900,"speed":0.000,"climb":0.000,"eps":25.59,"epc":73.60}
123 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ABANDONED PROJECT - pizero-gpslog
2 | =================================
3 |
4 | .. image:: https://www.repostatus.org/badges/latest/unsupported.svg
5 | :alt: Project Status: Unsupported – The project has reached a stable, usable state but the author(s) have ceased all work on it. A new maintainer may be desired.
6 | :target: https://www.repostatus.org/#unsupported
7 |
8 | .. image:: https://img.shields.io/pypi/v/pizero-gpslog.svg
9 | :alt: PyPI version badge
10 | :target: https://pypi.org/project/pizero-gpslog/
11 |
12 | Raspberry Pi Zero gpsd logger with status LEDs.
13 |
14 | .. image:: http://blog.jasonantman.com/GFX/pizero_gpslogger_1_sm.jpg
15 | :alt: Photograph of finished hardware next to playing card deck for size comparison
16 | :target: http://blog.jasonantman.com/GFX/pizero_gpslogger_1.jpg
17 |
18 | A longer description of the motivation behind this and the specific hardware that I used is available on my blog: `DIY Raspberry Pi Zero GPS Track Logger `_.
19 |
20 | Introduction and Goals
21 | ----------------------
22 |
23 | This is a trivial (and not really "supported") project of mine to couple a Raspberry Pi Zero with a USB GPS receiver and a battery pack to GPS track my hikes. The hardware decision was mainly based on what I had lying around: a Raspberry Pi Zero, a 10,000mAh external battery pack for my cell phone, and a USB GPS (well, I thought I had one, and got far enough into the project when I decided it was missing for good that I bought another).
24 |
25 | The goal is to package all of this together into a "small" (but not necessarily lightweight, based on the components) package that I can put in the outside pocket of my hiking pack, and record an accurate and detailed GPS track of my hikes. It writes the most recent position data from gpsd to disk at a user-defined interval, flushes IO after each write (so that it's safe to just pull the power on the Pi), and uses two LEDs to indicate status while in the field. Data is written in gpsd's native format, but a conversion tool is provided.
26 |
27 | This program relies on `gpsd `_ to interact with the GPS itself, as it's very mature and stable software, exposes a simple JSON-based socket interface, and also has decent Python bindings. There's no reason for a logger to have to worry about the nuances of GPS communication itself.
28 |
29 | Requirements
30 | ------------
31 |
32 | * Raspberry Pi (tested with `Pi Zero `_ 1.3) and a MicroSD card (I'm using an `8GB SanDisk Ultra Class 10 UHS-1 `_, which has enough space after the OS for 240 days of 5-second-interval data).
33 | * Raspberry Pi OS with Python3 (see installation instructions below)
34 | * `gpsd compatible `_ GPS (I use a `GlobalSat BU-353-S4 USB `_; the gpsd folks say some pretty awful things about it, but we'll see...)
35 | * Recommended, one of:
36 |
37 | * Two GPIO-connected LEDs on the RPi, ideally different colors (see below).
38 | * A bitmap display, such as the `Waveshare 2.13 inch E-Ink Display Hat (B) `__, which I got `on Amazon `__ for $25 USD. While e-Ink displays are comparatively sluggish (this one takes an astonishing 15 seconds to re-draw the screen), they offer some major advantages for this purpose: they have very low power consumption, and the displayed information stays visible until the next refresh even without power. This means that if you have a GPS display that refreshes every minute, it will still show the last coordinates as of when it lost power. The one I purchased is also fully assembled and just connects directly to the Pi's 40-pin header.
39 | * An `adafruit 4567 2.23" Monochrome OLED Bonnet `_ OLED. This is much brighter than the e-Ink and can refresh at up to 30Hz, but draws more power. Good if you're using this in your vehicle and have "unlimited" power.
40 | * Some other variety of display, if you're willing to write a driver class for it. See below for further information.
41 |
42 | * Some sort of power source for the Pi. I use a standard adapter when testing and a 10000mAh USB battery pack in the field (specifically the `Anker PowerCore Speed 10000 QA `_). That battery pack is extreme overkill, and powers the unit continuously for 42 hours, when logging at a 5-second interval.
43 |
44 | Software Installation
45 | ---------------------
46 |
47 | This assumes you're running on Linux...
48 |
49 | 1. Download the latest `Raspberry Pi OS Lite `_ image. I'm using the "May 2020" version, released 2020-05-27, kernel 4.19.
50 | 2. Verify the checksum on the file and then unzip it.
51 | 3. Put the MicroSD card in your machine and write the image to it. As root, ``dd bs=4M if=2020-05-27-raspios-buster-lite-armhf.img of=/dev/sdX conv=fsync status=progress`` (where ``/dev/sdX`` is the device that the SD card showed up at).
52 | 4. Wait for the copy to finish and IO to the device to stop (run ``sync``).
53 | 5. *Optional:* If you're going to be using this on a network (i.e. for setup, troubleshooting, development, etc.) then this would be a good time to mount the Raspian partition on your computer and make some changes. See `setup_pi.sh `_ for an example.
54 |
55 | 1. Copy your authorized_keys file over to ``/home/pi``.
56 | 2. Touch the file at ``/boot/ssh`` on the Pi boot partition to enable SSH access.
57 | 3. Set a hostname.
58 | 4. If desired, configure WiFi (as well as downloading the packages for any required drivers onto the OS partition).
59 | 5. When finished, unmount, ``sync`` and remove the SD card.
60 |
61 | 6. Put the SD card in your Pi and plug it in. If you're going to be connecting directly with a keyboard and monitor, do so. If you configured WiFi (or want to use a USB Ethernet adapter) and have everything setup correctly, it should eventually connect to your network. If you have issues with a USB Ethernet adapter, try letting the Pi boot up (give it 2-3 minutes) and *then* plug in the adapter.
62 | 7. Log in. The default user is named "pi" with a default password of "raspberry". Run sudo `raspi-config `_ to set things like the locale and timezone. Personally, I usually leave the ``pi`` user password at its default for devices that will never be on an untrusted network. If using a SPI display, enable the SPI kernel module via ``raspi-config``. If you're using an I2C display, enable the I2C module. Reboot as needed.
63 | 8. ``sudo apt-get update && sudo apt-get install haveged git python3-gpiozero python3-setuptools python3-pip gpsd python-gps python3-pillow``
64 | 9. If using a display such as the one recommended: ``sudo apt-get install python3-numpy && sudo pip3 install RPi.GPIO``
65 | 10. Run ``sudo pip3 install pizero-gpslog && sudo pizero-gpslog-install``. The installer, ``pizero-gpslog-install``, templates out a systemd unit file, reloads systemd, and enables the unit. Environment variables to set for the service are taken from command line arguments; see ``pizero-gpslog-install --help`` for details. They can be changed after install by editing ``/etc/systemd/system/pizero-gpslog.service``
66 | 11. Find out the USB vendor and product IDs for your GPS. My BU-353S4 uses a Prolific PL2303 serial chipset (vendor 067b product 2303) which is disabled by default in the Debian gpsd udev rules because it matches too many devices. Look at ``/lib/udev/rules.d/60-gpsd.rules``. If your GPS is commented out like mine, uncomment it and save the file.
67 | 12. If you're ready, ``sudo systemctl start pizero-gpslog`` to start it. Otherwise, it will start on the next boot.
68 |
69 | Hardware Installation & Setup
70 | -----------------------------
71 |
72 | GPS
73 | +++
74 |
75 | This should be pretty simple. Plug your GPS into the USB input on the RPi, via a "usb on the go" (USB OTG) cable.
76 |
77 | LED Indicators for GPS Fix & Disk Write
78 | +++++++++++++++++++++++++++++++++++++++
79 |
80 | The simplest status indication adds two LEDs to the Pi Zero. I prefer to solder a female right-angle header to the Pi, then put the LEDs on a male header so they can be removed. gpiozero, the library used for controlling the LEDs, has `pinout diagrams `_ and information on the `wiring that the API expects `_. The code this project uses expects the LEDs to be wired active-high (cathode to ground, anode to GPIO pin through a limiting resistor). I made up a small 8x20 header for my LEDs and (very sloppily) potted them in epoxy.
81 |
82 | The LEDs are configured using the ``LED_PIN_RED`` and ``LED_PIN_GREEN`` environment variables, as described in the Configuration section.
83 |
84 | The LED outputs are as follows:
85 |
86 | * Green Solid (at start) - connecting to gpsd. Green LED goes out when connected to gpsd and the output file is opened for writing.
87 | * Red Solid - no active GPS (gpsd does not yet have an active gps, or no GPS is connected).
88 | * Red 3 Fast Blinks (0.1 sec) - GPS is connected but does not yet have a fix.
89 | * Red 2 Slow Blinks (0.5 sec) - GPS has a 2D-only fix; position data is being read.
90 | * Red 1 Slow Blink (0.5s) - GPS has a 3D fix; position data is being read.
91 | * Green Blink (0.25s) - Data point written to disk (and flushed, if flush not disabled).
92 |
93 | Waveshare 2.13-inch e-Ink Display Hat B
94 | +++++++++++++++++++++++++++++++++++++++
95 |
96 | This 128x32 monochrome OLED display has a fixed pinout, and plugs directly in to the Pi's 40-pin GPIO header. **You must enable SPI via ``raspi-config`` before it will work.** The display is extremely sluggish, and takes approximately 15 seconds to redraw the image. It does not support partial re-draw, though some of their other models do.
97 |
98 | This display has a driver built-in to pizero-gpslog. To use the display, set ``DISPLAY_CLASS`` to ``pizero_gpslog.displays.epd2in13bc:EPD2in13bc``.
99 |
100 | Displays can be tested with some sample data using the ``pizero-gpslog-screentest`` entrypoint.
101 |
102 | Adafruit 4567 2.23" Monochrome OLED Bonnet
103 | ++++++++++++++++++++++++++++++++++++++++++
104 |
105 | This display uses I2C and connects to the Pi's 40-pin GPIO header. **You must enable I2C via ``raspi-config`` before it will work.** The display refreshes quite quickly (up to 30Hz) but draws considerably more power than the e-Ink displays.
106 |
107 | This display driver requires the installation of the `adafruit-circuitpython-ssd1305 `_ Python package.
108 |
109 | This display has a driver built-in to pizero-gpslog. To use the display, set ``DISPLAY_CLASS`` to ``pizero_gpslog.displays.adafruit4567:Adafruit4567``.
110 |
111 | Displays can be tested with some sample data using the ``pizero-gpslog-screentest`` entrypoint.
112 |
113 | Your Own Display
114 | ++++++++++++++++
115 |
116 | pizero-gpslog can support "any" display that's capable of rendering text. To implement a display driver class, create a subclass of ``pizero_gpslog.displays.base.BaseDisplay`` and implement at least the required methods and properties, as well as whatever internals your display needs. See the ``Adafruit4567`` class as an example. While it is strongly encouraged for you to contribute any display drivers back to the project via pull requests, the import system used can import any class from any importable module.
117 |
118 | Displays can be tested with some sample data using the ``pizero-gpslog-screentest`` entrypoint.
119 |
120 | Extra Data Providers
121 | --------------------
122 |
123 | It's possible to have a dict of arbitrary data from a "data provider" - a class to read any arbitrary sensor - included in each GPS location line in the output file. Extra Data Providers must be classes which are subclasses of ``pizero_gpslog.extradata.base.BaseExtraDataProvider``, implement all of its methods, and set ``self._data`` to a dict. the dict should have two keys: ``message``, a string message suitable for a line on a display (e.g. 20 characters or less), and ``data``, an arbitrary JSON-encodeable dict.
124 |
125 | Data providers are enabled by setting the ``EXTRA_DATA_CLASS`` environment variable to the module name and class name in colon-separated format.
126 |
127 | Two data providers are included:
128 |
129 | * Dummy ExtraData can be generated by running with ``EXTRA_DATA_CLASS=pizero_gpslog.extradata.dummy:DummyData``
130 | * GQ Electronics GMC-series geiger counter sensors can be enabled by running with ``EXTRA_DATA_CLASS=pizero_gpslog.extradata.gq_gmc500plus:GqGMC500plus``. This currently requires using my fork, i.e. ``pip install git+https://gitlab.com/jantman/gmc.git@jantman-fixes-config``
131 |
132 | Configuration
133 | -------------
134 |
135 | pizero-gpslog's entire configuration is provided via environment variables. There are NO command-line switches. By default, these are set in ``/etc/systemd/system/pizero-gpslog.service`` by the ``pizero-gpslog-install`` installer script and need to be updated in that file.
136 |
137 | * ``LOG_LEVEL`` - Defaults to "WARNING"; other accepted values are "INFO" and "DEBUG". All logging is to STDOUT.
138 | * ``LED_PIN_RED`` - Integer. Specifies the GPIO pin number used for the primary ("red") LED. Leave unset if running on non-RPi hardware (in which case LED state will be logged to STDOUT) or if using a display. Note the number used here is the Broadcom GPIO pin number, not the physical board pin number.
139 | * ``LED_PIN_GREEN`` - Integer. Specifies the GPIO pin number used for the secondary ("green") LED. Leave unset if running on non-RPi hardware (in which case LED state will be logged to STDOUT) or if using a display. Note the number used here is the Broadcom GPIO pin number, not the physical board pin number.
140 | * ``GPS_INTERVAL_SEC`` - Integer. Interval to poll gps at, and write gps position. Defaults to every 5 seconds.
141 | * ``FLUSH_FILE`` - String. If set to "false", do not explicitly flush output file after every write.
142 | * ``OUT_DIR`` - Directory to write log files under. If not set, will use current working directory (when running via systemd, as default, this will be the current directory that the installer was run in).
143 | * ``DISPLAY_CLASS`` - String. The colon-separated module path and class name of an importable class to drive a display. See details above on using displays.
144 | * ``DISPLAY_REFRESH_SEC`` - Integer. The ideal/target number of seconds between display refreshes. Note that how fast a display can actually refresh is hardware-specific, and how fast you *want* it to refresh is based on its power consumption and your battery life. The default value for this parameter is to refresh **as quickly as the display will allow!** If you use a fast display, you should set this to a sane integer.
145 |
146 | Running
147 | -------
148 |
149 | Configure as described above. Plug the Pi into a power source. Everything else should be automatic after the installation described above. The ``pizero-gpslog`` systemd service should start automatically at boot.
150 |
151 | Log Files
152 | +++++++++
153 |
154 | Log files will be written under the directory specified by the ``OUT_DIR`` environment variable, or the current working directory if that environment variable is not set. Log files will be written under that directory, named according to the time and date when the program started (``%Y-%m-%d_%H-%M-%S`` format).
155 |
156 | Each line of the output file is a single raw gpsd response to the ``?POLL`` command. While this program also decodes the responses, it doesn't make sense for us to invent our own data structure for something that already has one. Each line in the output file should be valid JSON matching the `gpsd JSON ?POLL response schema `_, deserialized and reserialized to ensure that it does not contain any linebreaks.
157 |
158 | Getting the Data
159 | ++++++++++++++++
160 |
161 | At the moment, when I'm home from a hike and the Pi is powered down, I just pull the SD card and copy the data to my computer, then delete the data file(s) from the SD card and put it back. It would certainly be easy to automate this with a Pi Zero W or an Ethernet or WiFi connection, but it's not worth it for me for this project. If you're interested, I have some scripts and instructions that might help as part of my `pi2graphite `_ project.
162 |
163 | Using the Data
164 | --------------
165 |
166 | The log files output by ``pizero-gpslog`` are in the `gpsd JSON ?POLL response format `_, one response per line (some responses may be empty). In order to make the output useful, this package also includes the ``pizero-gpslog-convert`` command line tool which can convert a specified JSON file to one of a variety of more-useful formats. While `gpsbabel `_ is the standard for GPS data format conversion, it doesn't support the gpsd POLL response format. This utility is provided as a means of converting to some common GPS data formats. If you need other formats, please convert to one of these and then to gpsbabel.
167 |
168 | * ``pizero-gpslog-convert YYYY-MM-DD_HH:MM:SS.json`` - convert ``YYYY-MM-DD_HH:MM:SS.json`` to GPX and write at ``YYYY-MM-DD_HH:MM:SS.gpx``
169 | * ``pizero-gpslog-convert --stats YYYY-MM-DD_HH:MM:SS.json`` - same as above, but also print some stats to STDERR
170 |
171 | It's up to you how to use the data, but there are a number of handy online tools that work with GPX files, including:
172 |
173 | * `gpsvisualizer.com `_ that has multiple output formats including `elevation and speed profiles `_ (and other profiles including slope, climb rate, pace, etc.), plotting the track `on Google Maps `_ (including with colorization by speed, elevation, slope, climb rate, pace, etc.), converting `to Google Earth KML `_, etc. Plotting can also use sources other than Google Maps, such as OpenStreetMap, ThunderForest, OpenTopoMap, USGS, USFS, etc. (and there's some `explanation `_ about how this is done).
174 | * `utrack.crempa.net `_ Takes a GPX file and generates a HTML page "report" giving a map overlay (with optional elevation colorization) as well as elevation and speed profiles (against both time and distance), some statistics, a distance vs time profile, and the option to download that report as a PDF.
175 | * `sunearthtools.com `_ has a simple tool (admittedly with a poor UI) that plots GPX data on Google maps along with a separate speed and elevation profile (by distance).
176 | * `mygpsfiles `_ Is a web-based app with a native-looking tiled UI that can plot tracks on Google Maps (Satellite or Map + Topo) as well as displaying per-point statistics (distance, time, elevation, speed, pace) and a configurable profile of elevation, speed, distance, pace, etc. As far as I can tell, all units are metric.
177 |
178 | Testing
179 | -------
180 |
181 | There currently aren't any code tests. But there are some scripts and tox-based helpers to aid with manual testing.
182 |
183 | * ``pizero_gpslog/tests/data/runfake.sh`` - Runs `gpsfake `_ (provided by gpsd) with sample data. Takes optional arguments for ``--nofix`` (data with no GPS fix) or ``--stillfix`` (fix but not moving).
184 | * Running with ``DISPLAY_CLASS=pizero_gpslog.displays.dummy:DummyDisplay`` will output display lines to STDOUT.
185 | * Dummy ExtraData can be generated by running with ``EXTRA_DATA_CLASS=pizero_gpslog.extradata.dummy:DummyData``.
186 |
187 | Development
188 | -----------
189 |
190 | Follow normal installation instructions, but instead of ``sudo pip3 install pizero-gpslog && sudo pizero-gpslog-install``, log in as ``pi``, and in ``/home/pi`` run ``git clone https://github.com/jantman/pizero-gpslog.git && cd pizero_gpslog/ && sudo python3 setup.py develop && sudo pizero-gpslog-install``.
191 |
192 | Release Process
193 | ---------------
194 |
195 | 1. Test changes locally, ensure they work as desired.
196 | 1. Ensure the version number has been incremented and there's an entry in ``CHANGES.rst``
197 | 1. Merge PR to ``master`` branch.
198 | 1. Manually tag master with the new version number and create a GitHub Release for it.
199 | 1. The above will trigger TravisCI to build and push to PyPI.
200 |
201 | Acknowledgements
202 | ----------------
203 |
204 | First, many thanks to the developers of gpsd, who have put forth the massive effort to make a script like this relatively trivial.
205 |
206 | Second, thanks to `Martijn Braam `_, developer of the MIT-licensed `gpsd-py3 `_ package. A modified version of that package makes up the ``gpsd.py`` module.
207 |
--------------------------------------------------------------------------------