commit
ca72ba8af5
531 changed files with 29349 additions and 11827 deletions
@ -1,3 +0,0 @@ |
||||
[run] |
||||
concurrency=multiprocessing |
||||
|
@ -1,26 +1,31 @@ |
||||
--- |
||||
name: Bug report |
||||
about: Create a report to help us improve openpilot |
||||
about: For issues with running openpilot on your comma device |
||||
title: '' |
||||
labels: '' |
||||
labels: 'bug' |
||||
assignees: '' |
||||
|
||||
--- |
||||
|
||||
**Describe the bug** |
||||
A clear and concise description of what the bug is. |
||||
|
||||
<!-- A clear and concise description of what the bug is. Add the `car bug` label for vehicle/brand specific bugs and the `bug` label for all other bugs. --> |
||||
|
||||
**How to reproduce or log data** |
||||
Steps to reproduce the behavior, or a explorer/cabana link to the exact drive and timestamp of when the bug occurred. |
||||
|
||||
<!-- Steps to reproduce the behavior. --> |
||||
|
||||
**Expected behavior** |
||||
A clear and concise description of what you expected to happen. |
||||
|
||||
<!-- A clear and concise description of what you expected to happen. --> |
||||
|
||||
**Device/Version information (please complete the following information):** |
||||
- Device: [e.g. EON/EON Gold] |
||||
- Dongle ID: [e.g. 77611a1fac303767, can be found in Settings -> Device -> Dongle ID] |
||||
- Version: [e.g. 0.6.4], or commit hash when on devel |
||||
- Car make/model [e.g. Toyota Prius 2016] |
||||
- Device: [e.g. EON/EON Gold/comma two] |
||||
- Dongle ID: [e.g. 77611a1fac303767, can be found in Settings -> Device -> Dongle ID or my.comma.ai/useradmin] |
||||
- Route: [e.g. 77611a1fac303767|2020-05-11--16-37-07, can be found in my.comma.ai/useradmin] |
||||
- Timestamp: [When in the route the bug occurs (e.g. 4min 30s into the drive)] |
||||
- Version: [commit hash when on a non-release branch, or version number when on devel or release2 (e.g. 0.7.6)] |
||||
- Car make/model: [e.g. Toyota Prius 2016] |
||||
|
||||
**Additional context** |
||||
Add any other context about the problem here. |
||||
|
||||
<!-- Add any other context about the problem here. --> |
||||
|
@ -0,0 +1,25 @@ |
||||
--- |
||||
name: PC Bug report |
||||
about: For issues with running openpilot on PC |
||||
title: '' |
||||
labels: 'PC' |
||||
assignees: '' |
||||
--- |
||||
|
||||
**Describe the bug** |
||||
|
||||
<!-- A clear and concise description of what the bug is. Add the `simulation` label if running in an environment like CARLA. --> |
||||
|
||||
**How to reproduce or log data** |
||||
|
||||
<!-- Steps to reproduce the behavior. --> |
||||
|
||||
**Expected behavior** |
||||
|
||||
<!-- A clear and concise description of what you expected to happen. --> |
||||
|
||||
**Additional context** |
||||
|
||||
<!-- Add any other context about the problem here. --> |
||||
|
||||
Operating system: [e.g. Ubuntu 16.04] |
@ -0,0 +1,8 @@ |
||||
blank_issues_enabled: false |
||||
contact_links: |
||||
- name: Community Wiki |
||||
url: https://github.com/commaai/openpilot/wiki |
||||
about: Check out our community wiki |
||||
- name: Community Discord |
||||
urli: https://discord.comma.ai |
||||
about: Check out our community discord |
@ -0,0 +1,8 @@ |
||||
--- |
||||
name: Enhancement |
||||
about: For suggestions for openpilot enhancements |
||||
title: '' |
||||
labels: 'enhancement' |
||||
assignees: '' |
||||
--- |
||||
|
@ -0,0 +1,17 @@ |
||||
--- |
||||
name: Question |
||||
about: For questions about openpilot |
||||
title: '' |
||||
labels: 'question' |
||||
assignees: '' |
||||
--- |
||||
|
||||
<!-- |
||||
|
||||
Consider these options before opening an issue for a question: |
||||
|
||||
- checking the FAQ at https://comma.ai/faq |
||||
- checking the wiki at https://wiki.comma.ai |
||||
- asking your question on our community discord at https://discord.comma.ai |
||||
|
||||
--> |
@ -0,0 +1,15 @@ |
||||
--- |
||||
name: Bug fix |
||||
about: For openpilot bug fixes |
||||
title: '' |
||||
labels: 'bugfix' |
||||
assignees: '' |
||||
--- |
||||
|
||||
**Description** |
||||
|
||||
<!-- A description of the bug and the fix. Also link the issue if it exists. --> |
||||
|
||||
**Verification** |
||||
|
||||
<!-- Explain how you tested this bug fix. --> |
@ -0,0 +1,19 @@ |
||||
--- |
||||
name: Car Bug fix |
||||
about: For vehicle/brand specifc bug fixes |
||||
title: '' |
||||
labels: 'car bug fix' |
||||
assignees: '' |
||||
--- |
||||
|
||||
**Description** |
||||
|
||||
<!-- A description of the bug and the fix. Also link the issue if it exists. --> |
||||
|
||||
**Verification** |
||||
|
||||
<!-- Explain how you tested this bug fix. --> |
||||
|
||||
**Route** |
||||
|
||||
Route: [a route with the bug fix] |
@ -0,0 +1,14 @@ |
||||
--- |
||||
name: Car port |
||||
about: For new car ports |
||||
title: '' |
||||
labels: 'car port' |
||||
assignees: '' |
||||
--- |
||||
|
||||
**Checklist** |
||||
|
||||
- [ ] added to README |
||||
- [ ] test route added to [test_car_models](../../selfdrive/test/test_car_models.py) |
||||
- [ ] route with openpilot: |
||||
- [ ] route with stock system: |
@ -0,0 +1,11 @@ |
||||
--- |
||||
name: Fingerprint |
||||
about: For adding fingerprints to existing cars |
||||
title: '' |
||||
labels: 'fingerprint' |
||||
assignees: '' |
||||
--- |
||||
|
||||
Discord username: [] |
||||
|
||||
Route: [] |
@ -0,0 +1,15 @@ |
||||
--- |
||||
name: Refactor |
||||
about: For code refactors |
||||
title: '' |
||||
labels: 'refactor' |
||||
assignees: '' |
||||
--- |
||||
|
||||
**Description** |
||||
|
||||
<!-- A description of the refactor, including the goals it accomplishes. --> |
||||
|
||||
**Verification** |
||||
|
||||
<!-- Explain how you tested the refactor for regressions. --> |
@ -0,0 +1,8 @@ |
||||
version: 2 |
||||
updates: |
||||
- package-ecosystem: pip |
||||
directory: "/" |
||||
schedule: |
||||
interval: daily |
||||
time: '15:00' |
||||
open-pull-requests-limit: 10 |
@ -0,0 +1,38 @@ |
||||
<!-- Please copy and paste the relevant template --> |
||||
|
||||
<!--- ***** Template: Car bug fix ***** |
||||
|
||||
**Description** [](A description of the bug and the fix. Also link any relevant issues.) |
||||
|
||||
**Verification** [](Explain how you tested this bug fix.) |
||||
|
||||
**Route** |
||||
Route: [a route with the bug fix] |
||||
|
||||
--> |
||||
|
||||
<!--- ***** Template: Bug fix ***** |
||||
|
||||
**Description** [](A description of the bug and the fix. Also link any relevant issues.) |
||||
|
||||
**Verification** [](Explain how you tested this bug fix.) |
||||
|
||||
--> |
||||
|
||||
<!--- ***** Template: Car port ***** |
||||
|
||||
**Checklist** |
||||
- [ ] added to README |
||||
- [ ] test route added to [test_car_models](../../selfdrive/test/test_car_models.py) |
||||
- [ ] route with openpilot: |
||||
- [ ] route with stock system: |
||||
|
||||
--> |
||||
|
||||
<!--- ***** Template: Refactor ***** |
||||
|
||||
**Description** [](A description of the refactor, including the goals it accomplishes.) |
||||
|
||||
**Verification** [](Explain how you tested the refactor for regressions.) |
||||
|
||||
--> |
@ -0,0 +1,23 @@ |
||||
name: "Update Pipfile.lock" |
||||
on: |
||||
schedule: |
||||
- cron: '00 15 * * 1' # Every monday on 15:00 UTC |
||||
|
||||
jobs: |
||||
piplock: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v2 |
||||
- uses: actions/setup-python@v2 |
||||
- run: pip install wheel |
||||
- run: pip install pipenv |
||||
- run: pipenv lock |
||||
- uses: actions/upload-artifact@v2 |
||||
with: |
||||
name: "Pipfile lock" |
||||
path: Pipfile.lock |
||||
- uses: peter-evans/create-pull-request@v2 |
||||
with: |
||||
title: "Update Pipfile.lock (dependencies)" |
||||
branch: update-pipfile |
||||
commit-message: "[Bot] Update Pipfile.lock dependencies" |
@ -0,0 +1,47 @@ |
||||
repos: |
||||
- repo: https://github.com/pre-commit/pre-commit-hooks |
||||
rev: master |
||||
hooks: |
||||
- id: check-ast |
||||
- id: check-json |
||||
- id: check-xml |
||||
- id: check-yaml |
||||
- id: check-merge-conflict |
||||
- id: check-symlinks |
||||
- repo: https://github.com/pre-commit/mirrors-mypy |
||||
rev: master |
||||
hooks: |
||||
- id: mypy |
||||
exclude: '^(pyextra)|(external)|(cereal)|(rednose)|(panda)|(laika)|(opendbc)|(laika_repo)|(rednose_repo)/' |
||||
additional_dependencies: ['git+https://github.com/numpy/numpy-stubs'] |
||||
- repo: https://github.com/PyCQA/flake8 |
||||
rev: master |
||||
hooks: |
||||
- id: flake8 |
||||
exclude: '^(pyextra)|(external)|(cereal)|(rednose)|(panda)|(laika)|(opendbc)|(laika_repo)|(rednose_repo)|(selfdrive/debug)/' |
||||
args: |
||||
- --select=F,E112,E113,E304,E501,E502,E701,E702,E703,E71,E72,E731,W191,W6 |
||||
- --max-line-length=240 |
||||
- --statistics |
||||
- repo: local |
||||
hooks: |
||||
- id: pylint |
||||
name: pylint |
||||
entry: pylint |
||||
language: system |
||||
types: [python] |
||||
exclude: '^(pyextra)|(external)|(cereal)|(rednose)|(panda)|(laika)|(laika_repo)|(rednose_repo)/' |
||||
- repo: local |
||||
hooks: |
||||
- id: cppcheck |
||||
name: cppcheck |
||||
entry: cppcheck |
||||
language: system |
||||
types: [c++] |
||||
exclude: '^(phonelibs)|(external)|(cereal)|(opendbc)|(panda)|(tools)|(selfdrive/modeld/thneed/debug)|(selfdrive/modeld/test)|(selfdrive/camerad/test)/|(installer)' |
||||
args: |
||||
- --error-exitcode=1 |
||||
- --language=c++ |
||||
- --quiet |
||||
- --force |
||||
- -j8 |
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,3 @@ |
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:d7b79038dccaa97d84bd38544573d3a52929770b76a259b25a27311464230e22 |
||||
size 13732809 |
||||
oid sha256:a198491887ed6029bffdf7f4dc28c4f9a6ba5f9d2235710fc11a1378893491d7 |
||||
size 13702777 |
||||
|
@ -1 +0,0 @@ |
||||
Subproject commit f5d2c1715c9482d898062110ce4c612093aa5d4f |
@ -1,6 +1,6 @@ |
||||
Import('env') |
||||
Import('env', 'cython_dependencies') |
||||
|
||||
# parser |
||||
env.Command(['common_pyx.so'], |
||||
['common_pyx_setup.py', 'clock.pyx'], |
||||
"cd common && python3 common_pyx_setup.py build_ext --inplace") |
||||
# Build cython clock module |
||||
env.Command(['common_pyx.so', 'clock.cpp'], |
||||
cython_dependencies + ['common_pyx_setup.py', 'clock.pyx'], |
||||
"cd common && python3 common_pyx_setup.py build_ext --inplace") |
||||
|
@ -1,3 +0,0 @@ |
||||
# py2,3 compatiblity helpers |
||||
|
||||
basestring = (str, bytes) |
@ -1,6 +1,6 @@ |
||||
Import('env') |
||||
Import('env', 'cython_dependencies') |
||||
|
||||
env.Command(['simple_kalman_impl.so'], |
||||
['simple_kalman_impl.pyx', 'simple_kalman_impl.pxd', 'simple_kalman_setup.py'], |
||||
"cd common/kalman && python3 simple_kalman_setup.py build_ext --inplace") |
||||
cython_dependencies + ['simple_kalman_impl.pyx', 'simple_kalman_impl.pxd', 'simple_kalman_setup.py'], |
||||
"cd common/kalman && python3 simple_kalman_setup.py build_ext --inplace") |
||||
|
||||
|
@ -1,50 +0,0 @@ |
||||
def cputime_total(ct): |
||||
return ct.cpuUser + ct.cpuSystem + ct.cpuChildrenUser + ct.cpuChildrenSystem |
||||
|
||||
|
||||
def print_cpu_usage(first_proc, last_proc): |
||||
r = 0 |
||||
procs = [ |
||||
("selfdrive.controls.controlsd", 59.46), |
||||
("./_modeld", 48.94), |
||||
("./loggerd", 28.49), |
||||
("selfdrive.controls.plannerd", 19.77), |
||||
("selfdrive.controls.radard", 9.54), |
||||
("./_ui", 9.54), |
||||
("./camerad", 7.07), |
||||
("selfdrive.locationd.locationd", 7.13), |
||||
("./_sensord", 6.17), |
||||
("selfdrive.controls.dmonitoringd", 5.48), |
||||
("./boardd", 3.63), |
||||
("./_dmonitoringmodeld", 2.67), |
||||
("selfdrive.logmessaged", 2.71), |
||||
("selfdrive.thermald", 2.41), |
||||
("./paramsd", 2.18), |
||||
("selfdrive.locationd.calibrationd", 1.76), |
||||
("./proclogd", 1.54), |
||||
("./_gpsd", 0.09), |
||||
("./clocksd", 0.02), |
||||
("./ubloxd", 0.02), |
||||
("selfdrive.tombstoned", 0), |
||||
("./logcatd", 0), |
||||
("selfdrive.updated", 0), |
||||
] |
||||
|
||||
dt = (last_proc.logMonoTime - first_proc.logMonoTime) / 1e9 |
||||
print("------------------------------------------------") |
||||
for proc_name, normal_cpu_usage in procs: |
||||
try: |
||||
first = [p for p in first_proc.procLog.procs if proc_name in p.cmdline][0] |
||||
last = [p for p in last_proc.procLog.procs if proc_name in p.cmdline][0] |
||||
cpu_time = cputime_total(last) - cputime_total(first) |
||||
cpu_usage = cpu_time / dt * 100. |
||||
if cpu_usage > max(normal_cpu_usage * 1.1, normal_cpu_usage + 5.0): |
||||
print(f"Warning {proc_name} using more CPU than normal") |
||||
r = 1 |
||||
|
||||
print(f"{proc_name.ljust(35)} {cpu_usage:.2f}%") |
||||
except IndexError: |
||||
print(f"{proc_name.ljust(35)} NO METRICS FOUND") |
||||
print("------------------------------------------------") |
||||
|
||||
return r |
@ -1,9 +0,0 @@ |
||||
import os |
||||
from nose.tools import nottest |
||||
|
||||
def phone_only(x): |
||||
if os.path.isfile("/init.qcom.rc"): |
||||
return x |
||||
else: |
||||
return nottest(x) |
||||
|
@ -0,0 +1,2 @@ |
||||
transformations |
||||
transformations.cpp |
@ -0,0 +1,8 @@ |
||||
Import('env', 'cython_dependencies') |
||||
|
||||
d = Dir('.') |
||||
|
||||
env.Command(['transformations.so'], |
||||
cython_dependencies + ['transformations.pxd', 'transformations.pyx', |
||||
'coordinates.cc', 'orientation.cc', 'coordinates.hpp', 'orientation.hpp'], |
||||
'cd ' + d.path + ' && python3 setup.py build_ext --inplace') |
@ -0,0 +1,104 @@ |
||||
#define _USE_MATH_DEFINES |
||||
|
||||
#include <iostream> |
||||
#include <cmath> |
||||
#include <eigen3/Eigen/Dense> |
||||
|
||||
#include "coordinates.hpp" |
||||
|
||||
#define DEG2RAD(x) ((x) * M_PI / 180.0) |
||||
#define RAD2DEG(x) ((x) * 180.0 / M_PI) |
||||
|
||||
|
||||
double a = 6378137; // lgtm [cpp/short-global-name]
|
||||
double b = 6356752.3142; // lgtm [cpp/short-global-name]
|
||||
double esq = 6.69437999014 * 0.001; // lgtm [cpp/short-global-name]
|
||||
double e1sq = 6.73949674228 * 0.001; |
||||
|
||||
|
||||
static Geodetic to_degrees(Geodetic geodetic){ |
||||
geodetic.lat = RAD2DEG(geodetic.lat); |
||||
geodetic.lon = RAD2DEG(geodetic.lon); |
||||
return geodetic; |
||||
} |
||||
|
||||
static Geodetic to_radians(Geodetic geodetic){ |
||||
geodetic.lat = DEG2RAD(geodetic.lat); |
||||
geodetic.lon = DEG2RAD(geodetic.lon); |
||||
return geodetic; |
||||
} |
||||
|
||||
|
||||
ECEF geodetic2ecef(Geodetic g){ |
||||
g = to_radians(g); |
||||
double xi = sqrt(1.0 - esq * pow(sin(g.lat), 2)); |
||||
double x = (a / xi + g.alt) * cos(g.lat) * cos(g.lon); |
||||
double y = (a / xi + g.alt) * cos(g.lat) * sin(g.lon); |
||||
double z = (a / xi * (1.0 - esq) + g.alt) * sin(g.lat); |
||||
return {x, y, z}; |
||||
} |
||||
|
||||
Geodetic ecef2geodetic(ECEF e){ |
||||
// Convert from ECEF to geodetic using Ferrari's methods
|
||||
// https://en.wikipedia.org/wiki/Geographic_coordinate_conversion#Ferrari.27s_solution
|
||||
double x = e.x; |
||||
double y = e.y; |
||||
double z = e.z; |
||||
|
||||
double r = sqrt(x * x + y * y); |
||||
double Esq = a * a - b * b; |
||||
double F = 54 * b * b * z * z; |
||||
double G = r * r + (1 - esq) * z * z - esq * Esq; |
||||
double C = (esq * esq * F * r * r) / (pow(G, 3)); |
||||
double S = cbrt(1 + C + sqrt(C * C + 2 * C)); |
||||
double P = F / (3 * pow((S + 1 / S + 1), 2) * G * G); |
||||
double Q = sqrt(1 + 2 * esq * esq * P); |
||||
double r_0 = -(P * esq * r) / (1 + Q) + sqrt(0.5 * a * a*(1 + 1.0 / Q) - P * (1 - esq) * z * z / (Q * (1 + Q)) - 0.5 * P * r * r); |
||||
double U = sqrt(pow((r - esq * r_0), 2) + z * z); |
||||
double V = sqrt(pow((r - esq * r_0), 2) + (1 - esq) * z * z); |
||||
double Z_0 = b * b * z / (a * V); |
||||
double h = U * (1 - b * b / (a * V)); |
||||
|
||||
double lat = atan((z + e1sq * Z_0) / r); |
||||
double lon = atan2(y, x); |
||||
|
||||
return to_degrees({lat, lon, h}); |
||||
} |
||||
|
||||
LocalCoord::LocalCoord(Geodetic g, ECEF e){ |
||||
init_ecef << e.x, e.y, e.z; |
||||
|
||||
g = to_radians(g); |
||||
|
||||
ned2ecef_matrix << |
||||
-sin(g.lat)*cos(g.lon), -sin(g.lon), -cos(g.lat)*cos(g.lon), |
||||
-sin(g.lat)*sin(g.lon), cos(g.lon), -cos(g.lat)*sin(g.lon), |
||||
cos(g.lat), 0, -sin(g.lat); |
||||
ecef2ned_matrix = ned2ecef_matrix.transpose(); |
||||
} |
||||
|
||||
NED LocalCoord::ecef2ned(ECEF e) { |
||||
Eigen::Vector3d ecef; |
||||
ecef << e.x, e.y, e.z; |
||||
|
||||
Eigen::Vector3d ned = (ecef2ned_matrix * (ecef - init_ecef)); |
||||
return {ned[0], ned[1], ned[2]}; |
||||
} |
||||
|
||||
ECEF LocalCoord::ned2ecef(NED n) { |
||||
Eigen::Vector3d ned; |
||||
ned << n.n, n.e, n.d; |
||||
|
||||
Eigen::Vector3d ecef = (ned2ecef_matrix * ned) + init_ecef; |
||||
return {ecef[0], ecef[1], ecef[2]}; |
||||
} |
||||
|
||||
NED LocalCoord::geodetic2ned(Geodetic g) { |
||||
ECEF e = ::geodetic2ecef(g); |
||||
return ecef2ned(e); |
||||
} |
||||
|
||||
Geodetic LocalCoord::ned2geodetic(NED n){ |
||||
ECEF e = ned2ecef(n); |
||||
return ::ecef2geodetic(e); |
||||
} |
@ -0,0 +1,35 @@ |
||||
#pragma once |
||||
|
||||
struct ECEF { |
||||
double x, y, z; |
||||
Eigen::Vector3d to_vector(){ |
||||
return Eigen::Vector3d(x, y, z); |
||||
} |
||||
}; |
||||
|
||||
struct NED { |
||||
double n, e, d; |
||||
}; |
||||
|
||||
struct Geodetic { |
||||
double lat, lon, alt; |
||||
bool radians=false; |
||||
}; |
||||
|
||||
ECEF geodetic2ecef(Geodetic g); |
||||
Geodetic ecef2geodetic(ECEF e); |
||||
|
||||
class LocalCoord { |
||||
public: |
||||
Eigen::Matrix3d ned2ecef_matrix; |
||||
Eigen::Matrix3d ecef2ned_matrix; |
||||
Eigen::Vector3d init_ecef; |
||||
LocalCoord(Geodetic g, ECEF e); |
||||
LocalCoord(Geodetic g) : LocalCoord(g, ::geodetic2ecef(g)) {} |
||||
LocalCoord(ECEF e) : LocalCoord(::ecef2geodetic(e), e) {} |
||||
|
||||
NED ecef2ned(ECEF e); |
||||
ECEF ned2ecef(NED n); |
||||
NED geodetic2ned(Geodetic g); |
||||
Geodetic ned2geodetic(NED n); |
||||
}; |
@ -1,108 +1,19 @@ |
||||
import numpy as np |
||||
""" |
||||
Coordinate transformation module. All methods accept arrays as input |
||||
with each row as a position. |
||||
""" |
||||
# pylint: skip-file |
||||
from common.transformations.orientation import numpy_wrap |
||||
from common.transformations.transformations import (ecef2geodetic_single, |
||||
geodetic2ecef_single) |
||||
from common.transformations.transformations import LocalCoord as LocalCoord_single |
||||
|
||||
|
||||
class LocalCoord(LocalCoord_single): |
||||
ecef2ned = numpy_wrap(LocalCoord_single.ecef2ned_single, (3,), (3,)) |
||||
ned2ecef = numpy_wrap(LocalCoord_single.ned2ecef_single, (3,), (3,)) |
||||
geodetic2ned = numpy_wrap(LocalCoord_single.geodetic2ned_single, (3,), (3,)) |
||||
ned2geodetic = numpy_wrap(LocalCoord_single.ned2geodetic_single, (3,), (3,)) |
||||
|
||||
a = 6378137 |
||||
b = 6356752.3142 |
||||
esq = 6.69437999014 * 0.001 |
||||
e1sq = 6.73949674228 * 0.001 |
||||
|
||||
geodetic2ecef = numpy_wrap(geodetic2ecef_single, (3,), (3,)) |
||||
ecef2geodetic = numpy_wrap(ecef2geodetic_single, (3,), (3,)) |
||||
|
||||
def geodetic2ecef(geodetic, radians=False): |
||||
geodetic = np.array(geodetic) |
||||
input_shape = geodetic.shape |
||||
geodetic = np.atleast_2d(geodetic) |
||||
|
||||
ratio = 1.0 if radians else (np.pi / 180.0) |
||||
lat = ratio*geodetic[:,0] |
||||
lon = ratio*geodetic[:,1] |
||||
alt = geodetic[:,2] |
||||
|
||||
xi = np.sqrt(1 - esq * np.sin(lat)**2) |
||||
x = (a / xi + alt) * np.cos(lat) * np.cos(lon) |
||||
y = (a / xi + alt) * np.cos(lat) * np.sin(lon) |
||||
z = (a / xi * (1 - esq) + alt) * np.sin(lat) |
||||
ecef = np.array([x, y, z]).T |
||||
return ecef.reshape(input_shape) |
||||
|
||||
|
||||
def ecef2geodetic(ecef, radians=False): |
||||
""" |
||||
Convert ECEF coordinates to geodetic using ferrari's method |
||||
""" |
||||
# Save shape and export column |
||||
ecef = np.atleast_1d(ecef) |
||||
input_shape = ecef.shape |
||||
ecef = np.atleast_2d(ecef) |
||||
x, y, z = ecef[:, 0], ecef[:, 1], ecef[:, 2] |
||||
|
||||
ratio = 1.0 if radians else (180.0 / np.pi) |
||||
|
||||
# Conver from ECEF to geodetic using Ferrari's methods |
||||
# https://en.wikipedia.org/wiki/Geographic_coordinate_conversion#Ferrari.27s_solution |
||||
r = np.sqrt(x * x + y * y) |
||||
Esq = a * a - b * b |
||||
F = 54 * b * b * z * z |
||||
G = r * r + (1 - esq) * z * z - esq * Esq |
||||
C = (esq * esq * F * r * r) / (pow(G, 3)) |
||||
S = np.cbrt(1 + C + np.sqrt(C * C + 2 * C)) |
||||
P = F / (3 * pow((S + 1 / S + 1), 2) * G * G) |
||||
Q = np.sqrt(1 + 2 * esq * esq * P) |
||||
r_0 = -(P * esq * r) / (1 + Q) + np.sqrt(0.5 * a * a*(1 + 1.0 / Q) - \ |
||||
P * (1 - esq) * z * z / (Q * (1 + Q)) - 0.5 * P * r * r) |
||||
U = np.sqrt(pow((r - esq * r_0), 2) + z * z) |
||||
V = np.sqrt(pow((r - esq * r_0), 2) + (1 - esq) * z * z) |
||||
Z_0 = b * b * z / (a * V) |
||||
h = U * (1 - b * b / (a * V)) |
||||
lat = ratio*np.arctan((z + e1sq * Z_0) / r) |
||||
lon = ratio*np.arctan2(y, x) |
||||
|
||||
# stack the new columns and return to the original shape |
||||
geodetic = np.column_stack((lat, lon, h)) |
||||
return geodetic.reshape(input_shape) |
||||
|
||||
class LocalCoord(): |
||||
""" |
||||
Allows conversions to local frames. In this case NED. |
||||
That is: North East Down from the start position in |
||||
meters. |
||||
""" |
||||
def __init__(self, init_geodetic, init_ecef): |
||||
self.init_ecef = init_ecef |
||||
lat, lon, _ = (np.pi/180)*np.array(init_geodetic) |
||||
self.ned2ecef_matrix = np.array([[-np.sin(lat)*np.cos(lon), -np.sin(lon), -np.cos(lat)*np.cos(lon)], |
||||
[-np.sin(lat)*np.sin(lon), np.cos(lon), -np.cos(lat)*np.sin(lon)], |
||||
[np.cos(lat), 0, -np.sin(lat)]]) |
||||
self.ecef2ned_matrix = self.ned2ecef_matrix.T |
||||
|
||||
@classmethod |
||||
def from_geodetic(cls, init_geodetic): |
||||
init_ecef = geodetic2ecef(init_geodetic) |
||||
return LocalCoord(init_geodetic, init_ecef) |
||||
|
||||
@classmethod |
||||
def from_ecef(cls, init_ecef): |
||||
init_geodetic = ecef2geodetic(init_ecef) |
||||
return LocalCoord(init_geodetic, init_ecef) |
||||
|
||||
|
||||
def ecef2ned(self, ecef): |
||||
ecef = np.array(ecef) |
||||
return np.dot(self.ecef2ned_matrix, (ecef - self.init_ecef).T).T |
||||
|
||||
def ned2ecef(self, ned): |
||||
ned = np.array(ned) |
||||
# Transpose so that init_ecef will broadcast correctly for 1d or 2d ned. |
||||
return (np.dot(self.ned2ecef_matrix, ned.T).T + self.init_ecef) |
||||
|
||||
def geodetic2ned(self, geodetic): |
||||
ecef = geodetic2ecef(geodetic) |
||||
return self.ecef2ned(ecef) |
||||
|
||||
def ned2geodetic(self, ned): |
||||
ecef = self.ned2ecef(ned) |
||||
return ecef2geodetic(ecef) |
||||
geodetic_from_ecef = ecef2geodetic |
||||
ecef_from_geodetic = geodetic2ecef |
||||
|
@ -0,0 +1,147 @@ |
||||
#define _USE_MATH_DEFINES |
||||
|
||||
#include <iostream> |
||||
#include <cmath> |
||||
#include <eigen3/Eigen/Dense> |
||||
|
||||
#include "orientation.hpp" |
||||
#include "coordinates.hpp" |
||||
|
||||
Eigen::Quaterniond ensure_unique(Eigen::Quaterniond quat){ |
||||
if (quat.w() > 0){ |
||||
return quat; |
||||
} else { |
||||
return Eigen::Quaterniond(-quat.w(), -quat.x(), -quat.y(), -quat.z()); |
||||
} |
||||
} |
||||
|
||||
Eigen::Quaterniond euler2quat(Eigen::Vector3d euler){ |
||||
Eigen::Quaterniond q; |
||||
|
||||
q = Eigen::AngleAxisd(euler(2), Eigen::Vector3d::UnitZ()) |
||||
* Eigen::AngleAxisd(euler(1), Eigen::Vector3d::UnitY()) |
||||
* Eigen::AngleAxisd(euler(0), Eigen::Vector3d::UnitX()); |
||||
return ensure_unique(q); |
||||
} |
||||
|
||||
|
||||
Eigen::Vector3d quat2euler(Eigen::Quaterniond quat){ |
||||
// TODO: switch to eigen implementation if the range of the Euler angles doesn't matter anymore
|
||||
// Eigen::Vector3d euler = quat.toRotationMatrix().eulerAngles(2, 1, 0);
|
||||
// return {euler(2), euler(1), euler(0)};
|
||||
double gamma = atan2(2 * (quat.w() * quat.x() + quat.y() * quat.z()), 1 - 2 * (quat.x()*quat.x() + quat.y()*quat.y())); |
||||
double theta = asin(2 * (quat.w() * quat.y() - quat.z() * quat.x())); |
||||
double psi = atan2(2 * (quat.w() * quat.z() + quat.x() * quat.y()), 1 - 2 * (quat.y()*quat.y() + quat.z()*quat.z())); |
||||
return {gamma, theta, psi}; |
||||
} |
||||
|
||||
Eigen::Matrix3d quat2rot(Eigen::Quaterniond quat){ |
||||
return quat.toRotationMatrix(); |
||||
} |
||||
|
||||
Eigen::Quaterniond rot2quat(Eigen::Matrix3d rot){ |
||||
return ensure_unique(Eigen::Quaterniond(rot)); |
||||
} |
||||
|
||||
Eigen::Matrix3d euler2rot(Eigen::Vector3d euler){ |
||||
return quat2rot(euler2quat(euler)); |
||||
} |
||||
|
||||
Eigen::Vector3d rot2euler(Eigen::Matrix3d rot){ |
||||
return quat2euler(rot2quat(rot)); |
||||
} |
||||
|
||||
Eigen::Matrix3d rot_matrix(double roll, double pitch, double yaw){ |
||||
return euler2rot({roll, pitch, yaw}); |
||||
} |
||||
|
||||
Eigen::Matrix3d rot(Eigen::Vector3d axis, double angle){ |
||||
Eigen::Quaterniond q; |
||||
q = Eigen::AngleAxisd(angle, axis); |
||||
return q.toRotationMatrix(); |
||||
} |
||||
|
||||
|
||||
Eigen::Vector3d ecef_euler_from_ned(ECEF ecef_init, Eigen::Vector3d ned_pose) { |
||||
/*
|
||||
Using Rotations to Build Aerospace Coordinate Systems |
||||
Don Koks |
||||
https://apps.dtic.mil/dtic/tr/fulltext/u2/a484864.pdf
|
||||
*/ |
||||
LocalCoord converter = LocalCoord(ecef_init); |
||||
Eigen::Vector3d zero = ecef_init.to_vector(); |
||||
|
||||
Eigen::Vector3d x0 = converter.ned2ecef({1, 0, 0}).to_vector() - zero; |
||||
Eigen::Vector3d y0 = converter.ned2ecef({0, 1, 0}).to_vector() - zero; |
||||
Eigen::Vector3d z0 = converter.ned2ecef({0, 0, 1}).to_vector() - zero; |
||||
|
||||
Eigen::Vector3d x1 = rot(z0, ned_pose(2)) * x0; |
||||
Eigen::Vector3d y1 = rot(z0, ned_pose(2)) * y0; |
||||
Eigen::Vector3d z1 = rot(z0, ned_pose(2)) * z0; |
||||
|
||||
Eigen::Vector3d x2 = rot(y1, ned_pose(1)) * x1; |
||||
Eigen::Vector3d y2 = rot(y1, ned_pose(1)) * y1; |
||||
Eigen::Vector3d z2 = rot(y1, ned_pose(1)) * z1; |
||||
|
||||
Eigen::Vector3d x3 = rot(x2, ned_pose(0)) * x2; |
||||
Eigen::Vector3d y3 = rot(x2, ned_pose(0)) * y2; |
||||
|
||||
|
||||
x0 = Eigen::Vector3d(1, 0, 0); |
||||
y0 = Eigen::Vector3d(0, 1, 0); |
||||
z0 = Eigen::Vector3d(0, 0, 1); |
||||
|
||||
double psi = atan2(x3.dot(y0), x3.dot(x0)); |
||||
double theta = atan2(-x3.dot(z0), sqrt(pow(x3.dot(x0), 2) + pow(x3.dot(y0), 2))); |
||||
|
||||
y2 = rot(z0, psi) * y0; |
||||
z2 = rot(y2, theta) * z0; |
||||
|
||||
double phi = atan2(y3.dot(z2), y3.dot(y2)); |
||||
|
||||
return {phi, theta, psi}; |
||||
} |
||||
|
||||
Eigen::Vector3d ned_euler_from_ecef(ECEF ecef_init, Eigen::Vector3d ecef_pose){ |
||||
/*
|
||||
Using Rotations to Build Aerospace Coordinate Systems |
||||
Don Koks |
||||
https://apps.dtic.mil/dtic/tr/fulltext/u2/a484864.pdf
|
||||
*/ |
||||
LocalCoord converter = LocalCoord(ecef_init); |
||||
|
||||
Eigen::Vector3d x0 = Eigen::Vector3d(1, 0, 0); |
||||
Eigen::Vector3d y0 = Eigen::Vector3d(0, 1, 0); |
||||
Eigen::Vector3d z0 = Eigen::Vector3d(0, 0, 1); |
||||
|
||||
Eigen::Vector3d x1 = rot(z0, ecef_pose(2)) * x0; |
||||
Eigen::Vector3d y1 = rot(z0, ecef_pose(2)) * y0; |
||||
Eigen::Vector3d z1 = rot(z0, ecef_pose(2)) * z0; |
||||
|
||||
Eigen::Vector3d x2 = rot(y1, ecef_pose(1)) * x1; |
||||
Eigen::Vector3d y2 = rot(y1, ecef_pose(1)) * y1; |
||||
Eigen::Vector3d z2 = rot(y1, ecef_pose(1)) * z1; |
||||
|
||||
Eigen::Vector3d x3 = rot(x2, ecef_pose(0)) * x2; |
||||
Eigen::Vector3d y3 = rot(x2, ecef_pose(0)) * y2; |
||||
|
||||
Eigen::Vector3d zero = ecef_init.to_vector(); |
||||
x0 = converter.ned2ecef({1, 0, 0}).to_vector() - zero; |
||||
y0 = converter.ned2ecef({0, 1, 0}).to_vector() - zero; |
||||
z0 = converter.ned2ecef({0, 0, 1}).to_vector() - zero; |
||||
|
||||
double psi = atan2(x3.dot(y0), x3.dot(x0)); |
||||
double theta = atan2(-x3.dot(z0), sqrt(pow(x3.dot(x0), 2) + pow(x3.dot(y0), 2))); |
||||
|
||||
y2 = rot(z0, psi) * y0; |
||||
z2 = rot(y2, theta) * z0; |
||||
|
||||
double phi = atan2(y3.dot(z2), y3.dot(y2)); |
||||
|
||||
return {phi, theta, psi}; |
||||
} |
||||
|
||||
|
||||
|
||||
int main(void){ |
||||
} |
@ -0,0 +1,17 @@ |
||||
#pragma once |
||||
#include <eigen3/Eigen/Dense> |
||||
#include "coordinates.hpp" |
||||
|
||||
|
||||
Eigen::Quaterniond ensure_unique(Eigen::Quaterniond quat); |
||||
|
||||
Eigen::Quaterniond euler2quat(Eigen::Vector3d euler); |
||||
Eigen::Vector3d quat2euler(Eigen::Quaterniond quat); |
||||
Eigen::Matrix3d quat2rot(Eigen::Quaterniond quat); |
||||
Eigen::Quaterniond rot2quat(Eigen::Matrix3d rot); |
||||
Eigen::Matrix3d euler2rot(Eigen::Vector3d euler); |
||||
Eigen::Vector3d rot2euler(Eigen::Matrix3d rot); |
||||
Eigen::Matrix3d rot_matrix(double roll, double pitch, double yaw); |
||||
Eigen::Matrix3d rot(Eigen::Vector3d axis, double angle); |
||||
Eigen::Vector3d ecef_euler_from_ned(ECEF ecef_init, Eigen::Vector3d ned_pose); |
||||
Eigen::Vector3d ned_euler_from_ecef(ECEF ecef_init, Eigen::Vector3d ecef_pose); |
@ -0,0 +1,42 @@ |
||||
import os |
||||
import numpy |
||||
import sysconfig |
||||
|
||||
from Cython.Build import cythonize |
||||
from Cython.Distutils import build_ext |
||||
from distutils.core import Extension, setup # pylint: disable=import-error,no-name-in-module |
||||
|
||||
def get_ext_filename_without_platform_suffix(filename): |
||||
name, ext = os.path.splitext(filename) |
||||
ext_suffix = sysconfig.get_config_var('EXT_SUFFIX') |
||||
|
||||
if ext_suffix == ext: |
||||
return filename |
||||
|
||||
ext_suffix = ext_suffix.replace(ext, '') |
||||
idx = name.find(ext_suffix) |
||||
|
||||
if idx == -1: |
||||
return filename |
||||
else: |
||||
return name[:idx] + ext |
||||
|
||||
|
||||
class BuildExtWithoutPlatformSuffix(build_ext): |
||||
def get_ext_filename(self, ext_name): |
||||
filename = super().get_ext_filename(ext_name) |
||||
return get_ext_filename_without_platform_suffix(filename) |
||||
|
||||
|
||||
setup( |
||||
name='Cython transformations wrapper', |
||||
cmdclass={'build_ext': BuildExtWithoutPlatformSuffix}, |
||||
ext_modules=cythonize( |
||||
Extension( |
||||
"transformations", |
||||
sources=["transformations.pyx"], |
||||
language="c++", |
||||
extra_compile_args=["-std=c++14"], |
||||
include_dirs=[numpy.get_include()], |
||||
) |
||||
)) |
@ -0,0 +1,71 @@ |
||||
from libcpp cimport bool |
||||
|
||||
cdef extern from "orientation.cc": |
||||
pass |
||||
|
||||
cdef extern from "orientation.hpp": |
||||
cdef cppclass Quaternion "Eigen::Quaterniond": |
||||
Quaternion() |
||||
Quaternion(double, double, double, double) |
||||
double w() |
||||
double x() |
||||
double y() |
||||
double z() |
||||
|
||||
cdef cppclass Vector3 "Eigen::Vector3d": |
||||
Vector3() |
||||
Vector3(double, double, double) |
||||
double operator()(int) |
||||
|
||||
cdef cppclass Matrix3 "Eigen::Matrix3d": |
||||
Matrix3() |
||||
Matrix3(double*) |
||||
|
||||
double operator()(int, int) |
||||
|
||||
Quaternion euler2quat(Vector3) |
||||
Vector3 quat2euler(Quaternion) |
||||
Matrix3 quat2rot(Quaternion) |
||||
Quaternion rot2quat(Matrix3) |
||||
Vector3 rot2euler(Matrix3) |
||||
Matrix3 euler2rot(Vector3) |
||||
Matrix3 rot_matrix(double, double, double) |
||||
Vector3 ecef_euler_from_ned(ECEF, Vector3) |
||||
Vector3 ned_euler_from_ecef(ECEF, Vector3) |
||||
|
||||
|
||||
cdef extern from "coordinates.cc": |
||||
cdef struct ECEF: |
||||
double x |
||||
double y |
||||
double z |
||||
|
||||
cdef struct NED: |
||||
double n |
||||
double e |
||||
double d |
||||
|
||||
cdef struct Geodetic: |
||||
double lat |
||||
double lon |
||||
double alt |
||||
bool radians |
||||
|
||||
ECEF geodetic2ecef(Geodetic) |
||||
Geodetic ecef2geodetic(ECEF) |
||||
|
||||
cdef cppclass LocalCoord_c "LocalCoord": |
||||
Matrix3 ned2ecef_matrix |
||||
Matrix3 ecef2ned_matrix |
||||
|
||||
LocalCoord_c(Geodetic, ECEF) |
||||
LocalCoord_c(Geodetic) |
||||
LocalCoord_c(ECEF) |
||||
|
||||
NED ecef2ned(ECEF) |
||||
ECEF ned2ecef(NED) |
||||
NED geodetic2ned(Geodetic) |
||||
Geodetic ned2geodetic(NED) |
||||
|
||||
cdef extern from "coordinates.hpp": |
||||
pass |
@ -0,0 +1,172 @@ |
||||
from transformations cimport Matrix3, Vector3, Quaternion |
||||
from transformations cimport ECEF, NED, Geodetic |
||||
|
||||
from transformations cimport euler2quat as euler2quat_c |
||||
from transformations cimport quat2euler as quat2euler_c |
||||
from transformations cimport quat2rot as quat2rot_c |
||||
from transformations cimport rot2quat as rot2quat_c |
||||
from transformations cimport euler2rot as euler2rot_c |
||||
from transformations cimport rot2euler as rot2euler_c |
||||
from transformations cimport rot_matrix as rot_matrix_c |
||||
from transformations cimport ecef_euler_from_ned as ecef_euler_from_ned_c |
||||
from transformations cimport ned_euler_from_ecef as ned_euler_from_ecef_c |
||||
from transformations cimport geodetic2ecef as geodetic2ecef_c |
||||
from transformations cimport ecef2geodetic as ecef2geodetic_c |
||||
from transformations cimport LocalCoord_c |
||||
|
||||
|
||||
import cython |
||||
import numpy as np |
||||
cimport numpy as np |
||||
|
||||
cdef np.ndarray[double, ndim=2] matrix2numpy(Matrix3 m): |
||||
return np.array([ |
||||
[m(0, 0), m(0, 1), m(0, 2)], |
||||
[m(1, 0), m(1, 1), m(1, 2)], |
||||
[m(2, 0), m(2, 1), m(2, 2)], |
||||
]) |
||||
|
||||
cdef Matrix3 numpy2matrix (np.ndarray[double, ndim=2, mode="fortran"] m): |
||||
assert m.shape[0] == 3 |
||||
assert m.shape[1] == 3 |
||||
return Matrix3(<double*>m.data) |
||||
|
||||
cdef ECEF list2ecef(ecef): |
||||
cdef ECEF e; |
||||
e.x = ecef[0] |
||||
e.y = ecef[1] |
||||
e.z = ecef[2] |
||||
return e |
||||
|
||||
cdef NED list2ned(ned): |
||||
cdef NED n; |
||||
n.n = ned[0] |
||||
n.e = ned[1] |
||||
n.d = ned[2] |
||||
return n |
||||
|
||||
cdef Geodetic list2geodetic(geodetic): |
||||
cdef Geodetic g |
||||
g.lat = geodetic[0] |
||||
g.lon = geodetic[1] |
||||
g.alt = geodetic[2] |
||||
return g |
||||
|
||||
def euler2quat_single(euler): |
||||
cdef Vector3 e = Vector3(euler[0], euler[1], euler[2]) |
||||
cdef Quaternion q = euler2quat_c(e) |
||||
return [q.w(), q.x(), q.y(), q.z()] |
||||
|
||||
def quat2euler_single(quat): |
||||
cdef Quaternion q = Quaternion(quat[0], quat[1], quat[2], quat[3]) |
||||
cdef Vector3 e = quat2euler_c(q); |
||||
return [e(0), e(1), e(2)] |
||||
|
||||
def quat2rot_single(quat): |
||||
cdef Quaternion q = Quaternion(quat[0], quat[1], quat[2], quat[3]) |
||||
cdef Matrix3 r = quat2rot_c(q) |
||||
return matrix2numpy(r) |
||||
|
||||
def rot2quat_single(rot): |
||||
cdef Matrix3 r = numpy2matrix(np.asfortranarray(rot, dtype=np.double)) |
||||
cdef Quaternion q = rot2quat_c(r) |
||||
return [q.w(), q.x(), q.y(), q.z()] |
||||
|
||||
def euler2rot_single(euler): |
||||
cdef Vector3 e = Vector3(euler[0], euler[1], euler[2]) |
||||
cdef Matrix3 r = euler2rot_c(e) |
||||
return matrix2numpy(r) |
||||
|
||||
def rot2euler_single(rot): |
||||
cdef Matrix3 r = numpy2matrix(np.asfortranarray(rot, dtype=np.double)) |
||||
cdef Vector3 e = rot2euler_c(r) |
||||
return [e(0), e(1), e(2)] |
||||
|
||||
def rot_matrix(roll, pitch, yaw): |
||||
return matrix2numpy(rot_matrix_c(roll, pitch, yaw)) |
||||
|
||||
def ecef_euler_from_ned_single(ecef_init, ned_pose): |
||||
cdef ECEF init = list2ecef(ecef_init) |
||||
cdef Vector3 pose = Vector3(ned_pose[0], ned_pose[1], ned_pose[2]) |
||||
|
||||
cdef Vector3 e = ecef_euler_from_ned_c(init, pose) |
||||
return [e(0), e(1), e(2)] |
||||
|
||||
def ned_euler_from_ecef_single(ecef_init, ecef_pose): |
||||
cdef ECEF init = list2ecef(ecef_init) |
||||
cdef Vector3 pose = Vector3(ecef_pose[0], ecef_pose[1], ecef_pose[2]) |
||||
|
||||
cdef Vector3 e = ned_euler_from_ecef_c(init, pose) |
||||
return [e(0), e(1), e(2)] |
||||
|
||||
def geodetic2ecef_single(geodetic): |
||||
cdef Geodetic g = list2geodetic(geodetic) |
||||
cdef ECEF e = geodetic2ecef_c(g) |
||||
return [e.x, e.y, e.z] |
||||
|
||||
def ecef2geodetic_single(ecef): |
||||
cdef ECEF e = list2ecef(ecef) |
||||
cdef Geodetic g = ecef2geodetic_c(e) |
||||
return [g.lat, g.lon, g.alt] |
||||
|
||||
|
||||
cdef class LocalCoord: |
||||
cdef LocalCoord_c * lc |
||||
|
||||
def __init__(self, geodetic=None, ecef=None): |
||||
assert (geodetic is not None) or (ecef is not None) |
||||
if geodetic is not None: |
||||
self.lc = new LocalCoord_c(list2geodetic(geodetic)) |
||||
elif ecef is not None: |
||||
self.lc = new LocalCoord_c(list2ecef(ecef)) |
||||
|
||||
@property |
||||
def ned2ecef_matrix(self): |
||||
return matrix2numpy(self.lc.ned2ecef_matrix) |
||||
|
||||
@property |
||||
def ecef2ned_matrix(self): |
||||
return matrix2numpy(self.lc.ecef2ned_matrix) |
||||
|
||||
@property |
||||
def ned_from_ecef_matrix(self): |
||||
return self.ecef2ned_matrix |
||||
|
||||
@property |
||||
def ecef_from_ned_matrix(self): |
||||
return self.ned2ecef_matrix |
||||
|
||||
@classmethod |
||||
def from_geodetic(cls, geodetic): |
||||
return cls(geodetic=geodetic) |
||||
|
||||
@classmethod |
||||
def from_ecef(cls, ecef): |
||||
return cls(ecef=ecef) |
||||
|
||||
def ecef2ned_single(self, ecef): |
||||
assert self.lc |
||||
cdef ECEF e = list2ecef(ecef) |
||||
cdef NED n = self.lc.ecef2ned(e) |
||||
return [n.n, n.e, n.d] |
||||
|
||||
def ned2ecef_single(self, ned): |
||||
assert self.lc |
||||
cdef NED n = list2ned(ned) |
||||
cdef ECEF e = self.lc.ned2ecef(n) |
||||
return [e.x, e.y, e.z] |
||||
|
||||
def geodetic2ned_single(self, geodetic): |
||||
assert self.lc |
||||
cdef Geodetic g = list2geodetic(geodetic) |
||||
cdef NED n = self.lc.geodetic2ned(g) |
||||
return [n.n, n.e, n.d] |
||||
|
||||
def ned2geodetic_single(self, ned): |
||||
assert self.lc |
||||
cdef NED n = list2ned(ned) |
||||
cdef Geodetic g = self.lc.ned2geodetic(n) |
||||
return [g.lat, g.lon, g.alt] |
||||
|
||||
def __dealloc__(self): |
||||
del self.lc |
Binary file not shown.
@ -1,3 +0,0 @@ |
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:e3c75edadf2e267be8a18870cafc911ea23d3b52dcff07fbe025edf815c06f5d |
||||
size 68732 |
@ -1 +0,0 @@ |
||||
/opt/intel/opencl-1.2-6.4.0.37/lib64/libintelocl.so |
@ -1,3 +0,0 @@ |
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:381814ea42344b895624597bf31c24ebc13f3449aaaaf65245f4a041d953b4c6 |
||||
size 17276236 |
@ -1,3 +0,0 @@ |
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:25aaa33f5c338b6dcc33436fdebb5e6ad727cf85a9fae921be8d3b834166ab01 |
||||
size 11432 |
@ -1,3 +0,0 @@ |
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:f7300ebee63820b519c54a50c93847516dcaf37765a698826fde666990747459 |
||||
size 20290860 |
@ -1,9 +0,0 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
# only pyflakes check (--select=F) |
||||
RESULT=$(python3 -m flake8 --select=F $(eval echo $(cat <(find cereal) <(find opendbc) release/files_common release/files_common | tr '\n' ' ') | tr ' ' '\n' | grep "\.py$")) |
||||
if [[ $RESULT ]]; then |
||||
echo "Pyflakes found errors in the code. Please fix and try again" |
||||
echo "$RESULT" |
||||
exit 1 |
||||
fi |
@ -0,0 +1,82 @@ |
||||
#!/usr/bin/env python3 |
||||
import os |
||||
import shutil |
||||
import subprocess |
||||
import tempfile |
||||
import time |
||||
import unittest |
||||
|
||||
from common.basedir import BASEDIR |
||||
|
||||
UPDATER_PATH = os.path.join(BASEDIR, "installer/updater") |
||||
UPDATER = os.path.join(UPDATER_PATH, "updater") |
||||
UPDATE_MANIFEST = os.path.join(UPDATER_PATH, "update.json") |
||||
|
||||
|
||||
class TestUpdater(unittest.TestCase): |
||||
|
||||
@classmethod |
||||
def setUpClass(cls): |
||||
# test that the updater builds |
||||
cls.assertTrue(f"cd {UPDATER_PATH} && make clean && make", "updater failed to build") |
||||
|
||||
# restore the checked-in version, since that's what actually runs on devices |
||||
os.system(f"git reset --hard {UPDATER_PATH}") |
||||
|
||||
def setUp(self): |
||||
self._clear_dir() |
||||
|
||||
def tearDown(self): |
||||
self._clear_dir() |
||||
|
||||
def _clear_dir(self): |
||||
if os.path.isdir("/data/neoupdate"): |
||||
shutil.rmtree("/data/neoupdate") |
||||
|
||||
def _assert_ok(self, cmd, msg=None): |
||||
self.assertTrue(os.system(cmd) == 0, msg) |
||||
|
||||
def _assert_fails(self, cmd): |
||||
self.assertFalse(os.system(cmd) == 0) |
||||
|
||||
def test_background_download(self): |
||||
self._assert_ok(f"{UPDATER} bgcache 'file://{UPDATE_MANIFEST}'") |
||||
|
||||
def test_background_download_bad_manifest(self): |
||||
# update with bad manifest should fail |
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".json") as f: |
||||
f.write("{}") |
||||
self._assert_fails(f"{UPDATER} bgcache 'file://{f.name}'") |
||||
|
||||
def test_cache_resume(self): |
||||
self._assert_ok(f"{UPDATER} bgcache 'file://{UPDATE_MANIFEST}'") |
||||
# a full download takes >1m, but resuming from fully cached should only be a few seconds |
||||
start_time = time.monotonic() |
||||
self._assert_ok(f"{UPDATER} bgcache 'file://{UPDATE_MANIFEST}'") |
||||
self.assertLess(time.monotonic() - start_time, 10) |
||||
|
||||
# make sure we can recover from corrupt downloads |
||||
def test_recover_from_corrupt(self): |
||||
# download the whole update |
||||
self._assert_ok(f"{UPDATER} bgcache 'file://{UPDATE_MANIFEST}'") |
||||
|
||||
# write some random bytes |
||||
for f in os.listdir("/data/neoupdate"): |
||||
with open(os.path.join("/data/neoupdate", f), "ab") as f: |
||||
f.write(b"\xab"*20) |
||||
|
||||
# this attempt should fail, then it unlinks |
||||
self._assert_fails(f"{UPDATER} bgcache 'file://{UPDATE_MANIFEST}'") |
||||
|
||||
# now it should pass |
||||
self._assert_ok(f"{UPDATER} bgcache 'file://{UPDATE_MANIFEST}'") |
||||
|
||||
# simple test that the updater doesn't crash in UI mode |
||||
def test_ui_init(self): |
||||
with subprocess.Popen(UPDATER) as proc: |
||||
time.sleep(5) |
||||
self.assertTrue(proc.poll() is None) |
||||
proc.terminate() |
||||
|
||||
if __name__ == "__main__": |
||||
unittest.main() |
@ -0,0 +1,7 @@ |
||||
{ |
||||
"ota_url": "https://commadist.azureedge.net/neosupdate/ota-signed-3bd2b3bdd6a501569e00b8f12786d65e0fd2788c0dd238f8c986e3e2e504683a-kernel.zip", |
||||
"ota_hash": "3bd2b3bdd6a501569e00b8f12786d65e0fd2788c0dd238f8c986e3e2e504683a", |
||||
"recovery_url": "https://commadist.azureedge.net/neosupdate/recovery-97c27e6ed04ed6bb0608b845a2d4100912093f9380c3f2ba6b56bccd608e5f6e.img", |
||||
"recovery_len": 15861036, |
||||
"recovery_hash": "97c27e6ed04ed6bb0608b845a2d4100912093f9380c3f2ba6b56bccd608e5f6e" |
||||
} |
Binary file not shown.
@ -1 +1 @@ |
||||
Subproject commit d172b27f4f346e642802a4c7cbf14405a4161d35 |
||||
Subproject commit 765c6584c3d7f27e1af0f1180cc29766d5319f09 |
@ -0,0 +1,17 @@ |
||||
#!/usr/bin/bash |
||||
|
||||
export OMP_NUM_THREADS=1 |
||||
export MKL_NUM_THREADS=1 |
||||
export NUMEXPR_NUM_THREADS=1 |
||||
export OPENBLAS_NUM_THREADS=1 |
||||
export VECLIB_MAXIMUM_THREADS=1 |
||||
|
||||
if [ -z "$REQUIRED_NEOS_VERSION" ]; then |
||||
export REQUIRED_NEOS_VERSION="14" |
||||
fi |
||||
|
||||
if [ -z "$PASSIVE" ]; then |
||||
export PASSIVE="1" |
||||
fi |
||||
|
||||
export STAGING_ROOT="/data/safe_staging" |
@ -1 +1 @@ |
||||
43221d85-46fd-40b9-bff0-2b1b18a86b07 |
||||
e96f9be6-5741-42ea-bdcd-0be6515b4230 |
@ -1,3 +1,3 @@ |
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:5c39a2096f7058541b5339ec36bc4c468955e67285078080ed6d8802fed06c1d |
||||
size 814176 |
||||
oid sha256:09aa11a17a5a8173e231071898c499f9ea632e6e64285586122828b1bbc70d41 |
||||
size 4165968 |
||||
|
@ -1,3 +1,3 @@ |
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:29504dfd101ba2a0b48550fac2f86f9d0b8d1245af3d2d8d658247b4a73077a2 |
||||
size 230121 |
||||
oid sha256:beecf140ddc5da96cbdae3b869ebb3f5453dcd8e61e09d7d079c91e006b6df98 |
||||
size 1134208 |
||||
|
@ -0,0 +1,4 @@ |
||||
[mypy] |
||||
python_version = 3.8 |
||||
ignore_missing_imports = True |
||||
|
@ -1 +1 @@ |
||||
Subproject commit 45c0d9ecce255e028163539e22e9a169735de69d |
||||
Subproject commit b7cf1a67bc71b674e6793ba1f2fff5d29fee1e6b |
@ -1 +1 @@ |
||||
Subproject commit 6b19fa4961d5dc6e6ea77987eb3a99ce28b0f5cd |
||||
Subproject commit ecef0a19d0f72d8fd3151593b7bd1a112d5f63e2 |
@ -1 +0,0 @@ |
||||
from .utils import LogentriesHandler |
@ -1,49 +0,0 @@ |
||||
|
||||
""" This file contains some helpers methods in both Python2 and 3 """ |
||||
import sys |
||||
import re |
||||
|
||||
if sys.version < '3': |
||||
# Python2.x imports |
||||
import Queue |
||||
import codecs |
||||
else: |
||||
# Python 3.x imports |
||||
import queue |
||||
|
||||
|
||||
def check_token(token): |
||||
""" Checks if the given token is a valid UUID.""" |
||||
valid = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-" |
||||
r"[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$") |
||||
|
||||
return valid.match(token) |
||||
|
||||
# We need to do some things different pending if its Python 2.x or 3.x |
||||
if sys.version < '3': |
||||
def to_unicode(ch): |
||||
return codecs.unicode_escape_decode(ch)[0] |
||||
|
||||
def is_unicode(ch): |
||||
return isinstance(ch, unicode) |
||||
|
||||
def create_unicode(ch): |
||||
try: |
||||
return unicode(ch, 'utf-8') |
||||
except UnicodeDecodeError as e: |
||||
return str(e) |
||||
|
||||
def create_queue(max_size): |
||||
return Queue.Queue(max_size) |
||||
else: |
||||
def to_unicode(ch): |
||||
return ch |
||||
|
||||
def is_unicode(ch): |
||||
return isinstance(ch, str) |
||||
|
||||
def create_unicode(ch): |
||||
return str(ch) |
||||
|
||||
def create_queue(max_size): |
||||
return queue.Queue(max_size) |
@ -1,57 +0,0 @@ |
||||
from logentries import LogentriesHandler |
||||
from threading import Lock |
||||
from functools import wraps |
||||
import logging |
||||
import time |
||||
import sys |
||||
import psutil |
||||
|
||||
glob_time = 0 |
||||
glob_name = 0 |
||||
|
||||
log = logging.getLogger('logentries') |
||||
log.setLevel(logging.INFO) |
||||
|
||||
class Metric(object): |
||||
|
||||
def __init__(self, token): |
||||
self._count = 0.0 |
||||
self._sum = 0.0 |
||||
self._lock = Lock() |
||||
self.token = token |
||||
handler = LogentriesHandler(token) |
||||
log.addHandler(handler) |
||||
|
||||
def observe(self, amount): |
||||
with self._lock: |
||||
self._count += 1 |
||||
self._sum += amount |
||||
|
||||
def metric(self): |
||||
'''Mesaure function execution time in seconds |
||||
and forward it to Logentries''' |
||||
|
||||
class Timer(object): |
||||
|
||||
def __init__(self, summary): |
||||
self._summary = summary |
||||
|
||||
def __enter__(self): |
||||
self._start = time.time() |
||||
|
||||
def __exit__(self, typ, value, traceback): |
||||
global glob_time |
||||
self._summary.observe(max(time.time() - self._start, 0)) |
||||
glob_time = time.time()- self._start |
||||
log.info("function_name=" + glob_name + " " + "execution_time=" + str(glob_time) + " " + "cpu=" + str(psutil.cpu_percent(interval=None)) + " " + "cpu_count=" + str(psutil.cpu_count())+ " " + "memory=" + str(psutil.virtual_memory()) ) |
||||
|
||||
def __call__(self, f): |
||||
@wraps(f) |
||||
def wrapped(*args, **kwargs): |
||||
with self: |
||||
global glob_name |
||||
glob_name = f.__name__ |
||||
|
||||
return f(*args, **kwargs) |
||||
return wrapped |
||||
return Timer(self) |
@ -1,218 +0,0 @@ |
||||
# coding: utf-8 |
||||
# vim: set ts=4 sw=4 et: |
||||
""" This file contains some utils for connecting to Logentries |
||||
as well as storing logs in a queue and sending them.""" |
||||
|
||||
VERSION = '2.0.7' |
||||
|
||||
from logentries import helpers as le_helpers |
||||
|
||||
import logging |
||||
import threading |
||||
import socket |
||||
import random |
||||
import time |
||||
import sys |
||||
|
||||
import certifi |
||||
|
||||
|
||||
# Size of the internal event queue |
||||
QUEUE_SIZE = 32768 |
||||
# Logentries API server address |
||||
LE_API_DEFAULT = "data.logentries.com" |
||||
# Port number for token logging to Logentries API server |
||||
LE_PORT_DEFAULT = 80 |
||||
LE_TLS_PORT_DEFAULT = 443 |
||||
# Minimal delay between attempts to reconnect in seconds |
||||
MIN_DELAY = 0.1 |
||||
# Maximal delay between attempts to recconect in seconds |
||||
MAX_DELAY = 10 |
||||
# Unicode Line separator character \u2028 |
||||
LINE_SEP = le_helpers.to_unicode('\u2028') |
||||
|
||||
|
||||
# LE appender signature - used for debugging messages |
||||
LE = "LE: " |
||||
# Error message displayed when an incorrect Token has been detected |
||||
INVALID_TOKEN = ("\n\nIt appears the LOGENTRIES_TOKEN " |
||||
"parameter you entered is incorrect!\n\n") |
||||
|
||||
|
||||
def dbg(msg): |
||||
print(LE + msg) |
||||
|
||||
|
||||
class PlainTextSocketAppender(threading.Thread): |
||||
def __init__(self, verbose=True, le_api=LE_API_DEFAULT, le_port=LE_PORT_DEFAULT, le_tls_port=LE_TLS_PORT_DEFAULT): |
||||
threading.Thread.__init__(self) |
||||
|
||||
# Logentries API server address |
||||
self.le_api = le_api |
||||
|
||||
# Port number for token logging to Logentries API server |
||||
self.le_port = le_port |
||||
self.le_tls_port = le_tls_port |
||||
|
||||
self.daemon = True |
||||
self.verbose = verbose |
||||
self._conn = None |
||||
self._queue = le_helpers.create_queue(QUEUE_SIZE) |
||||
|
||||
def empty(self): |
||||
return self._queue.empty() |
||||
|
||||
def open_connection(self): |
||||
self._conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
||||
self._conn.connect((self.le_api, self.le_port)) |
||||
|
||||
def reopen_connection(self): |
||||
self.close_connection() |
||||
|
||||
root_delay = MIN_DELAY |
||||
while True: |
||||
try: |
||||
self.open_connection() |
||||
return |
||||
except Exception: |
||||
if self.verbose: |
||||
dbg("Unable to connect to Logentries") |
||||
|
||||
root_delay *= 2 |
||||
if(root_delay > MAX_DELAY): |
||||
root_delay = MAX_DELAY |
||||
|
||||
wait_for = root_delay + random.uniform(0, root_delay) |
||||
|
||||
try: |
||||
time.sleep(wait_for) |
||||
except KeyboardInterrupt: |
||||
raise |
||||
|
||||
def close_connection(self): |
||||
if self._conn is not None: |
||||
self._conn.close() |
||||
|
||||
def run(self): |
||||
try: |
||||
# Open connection |
||||
self.reopen_connection() |
||||
|
||||
# Send data in queue |
||||
while True: |
||||
# Take data from queue |
||||
data = self._queue.get(block=True) |
||||
|
||||
# Replace newlines with Unicode line separator |
||||
# for multi-line events |
||||
if not le_helpers.is_unicode(data): |
||||
multiline = le_helpers.create_unicode(data).replace( |
||||
'\n', LINE_SEP) |
||||
else: |
||||
multiline = data.replace('\n', LINE_SEP) |
||||
multiline += "\n" |
||||
# Send data, reconnect if needed |
||||
while True: |
||||
try: |
||||
self._conn.send(multiline.encode('utf-8')) |
||||
except socket.error: |
||||
self.reopen_connection() |
||||
continue |
||||
break |
||||
except KeyboardInterrupt: |
||||
if self.verbose: |
||||
dbg("Logentries asynchronous socket client interrupted") |
||||
|
||||
self.close_connection() |
||||
|
||||
SocketAppender = PlainTextSocketAppender |
||||
|
||||
try: |
||||
import ssl |
||||
ssl_enabled = True |
||||
except ImportError: # for systems without TLS support. |
||||
ssl_enabled = False |
||||
dbg("Unable to import ssl module. Will send over port 80.") |
||||
else: |
||||
class TLSSocketAppender(PlainTextSocketAppender): |
||||
|
||||
def open_connection(self): |
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
||||
sock = ssl.wrap_socket( |
||||
sock=sock, |
||||
keyfile=None, |
||||
certfile=None, |
||||
server_side=False, |
||||
cert_reqs=ssl.CERT_REQUIRED, |
||||
ssl_version=getattr( |
||||
ssl, |
||||
'PROTOCOL_TLSv1_2', |
||||
ssl.PROTOCOL_TLSv1 |
||||
), |
||||
ca_certs=certifi.where(), |
||||
do_handshake_on_connect=True, |
||||
suppress_ragged_eofs=True, |
||||
) |
||||
|
||||
sock.connect((self.le_api, self.le_tls_port)) |
||||
self._conn = sock |
||||
|
||||
|
||||
class LogentriesHandler(logging.Handler): |
||||
def __init__(self, token, use_tls=True, verbose=True, format=None, le_api=LE_API_DEFAULT, le_port=LE_PORT_DEFAULT, le_tls_port=LE_TLS_PORT_DEFAULT): |
||||
logging.Handler.__init__(self) |
||||
self.token = token |
||||
self.good_config = True |
||||
self.verbose = verbose |
||||
# give the socket 10 seconds to flush, |
||||
# otherwise drop logs |
||||
self.timeout = 10 |
||||
if not le_helpers.check_token(token): |
||||
if self.verbose: |
||||
dbg(INVALID_TOKEN) |
||||
self.good_config = False |
||||
if format is None: |
||||
format = logging.Formatter('%(asctime)s : %(levelname)s, %(message)s', |
||||
'%a %b %d %H:%M:%S %Z %Y') |
||||
self.setFormatter(format) |
||||
self.setLevel(logging.DEBUG) |
||||
if use_tls and ssl_enabled: |
||||
self._thread = TLSSocketAppender(verbose=verbose, le_api=le_api, le_port=le_port, le_tls_port=le_tls_port) |
||||
else: |
||||
self._thread = SocketAppender(verbose=verbose, le_api=le_api, le_port=le_port, le_tls_port=le_tls_port) |
||||
|
||||
def flush(self): |
||||
# wait for all queued logs to be send |
||||
now = time.time() |
||||
while not self._thread.empty(): |
||||
time.sleep(0.2) |
||||
if time.time() - now > self.timeout: |
||||
break |
||||
|
||||
def emit_raw(self, msg): |
||||
if self.good_config and not self._thread.is_alive(): |
||||
try: |
||||
self._thread.start() |
||||
if self.verbose: |
||||
dbg("Starting Logentries Asynchronous Socket Appender") |
||||
except RuntimeError: # It's already started. |
||||
pass |
||||
|
||||
msg = self.token + msg |
||||
try: |
||||
self._thread._queue.put_nowait(msg) |
||||
except Exception: |
||||
# Queue is full, try to remove the oldest message and put again |
||||
try: |
||||
self._thread._queue.get_nowait() |
||||
self._thread._queue.put_nowait(msg) |
||||
except Exception: |
||||
# Race condition, no need for any action here |
||||
pass |
||||
|
||||
def emit(self, record): |
||||
msg = self.format(record).rstrip('\n') |
||||
self.emit_raw(msg) |
||||
|
||||
def close(self): |
||||
logging.Handler.close(self) |
@ -1,11 +0,0 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
python3 -m pylint --disable=R,C,W $(eval echo <(find cereal) <(find opendbc) $(cat release/files_common release/files_common | tr '\n' ' ') | tr ' ' '\n' | grep "\.py$") |
||||
|
||||
exit_status=$? |
||||
(( res = exit_status & 3 )) |
||||
|
||||
if [[ $res != 0 ]]; then |
||||
echo "Pylint found errors in the code. Please fix and try again" |
||||
exit 1 |
||||
fi |
@ -1 +1 @@ |
||||
Subproject commit d3a79c6a421b4eec952eeb8d1546a4c3c3ff030e |
||||
Subproject commit 2e556b8219185708ed974a4b6502796607d7ce0d |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue