mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-26 02:33:46 +08:00
Compare commits
182 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59bd58c940 | |||
| 105b95ffd7 | |||
| fdabd57e7e | |||
| 719e0572e1 | |||
| 0ad5715255 | |||
| 30dffb486a | |||
| 0728e57578 | |||
| c2d5deeccc | |||
| d0c1f9a864 | |||
| 920572442b | |||
| c50c718293 | |||
| 6b1506740e | |||
| f50e016c12 | |||
| bc725b98c9 | |||
| 9c5e035838 | |||
| 6d5cde3412 | |||
| 07264699b7 | |||
| 37a73d5c94 | |||
| 79c149f42d | |||
| 47f4396d7d | |||
| e8959e6b1a | |||
| c770f54103 | |||
| a98000849e | |||
| e32e754a74 | |||
| 118198605f | |||
| 0fb9971728 | |||
| 2fe75cb7d0 | |||
| 6797910ab0 | |||
| 5ea95c3d87 | |||
| fe250d6a9e | |||
| 58f4f5aac4 | |||
| 0e1fc45bc3 | |||
| 64feca5692 | |||
| 1db7b43870 | |||
| 9b1f2d5c64 | |||
| a53577ed35 | |||
| 77cb0b1464 | |||
| f609507afd | |||
| 98f5d30455 | |||
| f610e596c4 | |||
| 7c94b36171 | |||
| 39c9c85562 | |||
| 831cb68a64 | |||
| 5aff2b6885 | |||
| 811b3b7a9a | |||
| c42e2ecc50 | |||
| 0470b25071 | |||
| f72f78f2b9 | |||
| df6fa7acbd | |||
| d4c3c7f6f8 | |||
| a7efc8a897 | |||
| 822b32656b | |||
| bfa2d030d2 | |||
| 3f92bc2061 | |||
| 770903520d | |||
| 75fdebb1ee | |||
| c9d49b8e4a | |||
| 6322a275d6 | |||
| b7aeb5d64d | |||
| 58262bac9d | |||
| a75891f099 | |||
| a053244a5a | |||
| 356f353489 | |||
| c9501cc164 | |||
| 4dac1128f1 | |||
| 3a65206c8d | |||
| 4d5957432e | |||
| 3ebb0bc46f | |||
| a2fe62ce77 | |||
| a97ebc28b5 | |||
| ada141e5e3 | |||
| 0319861700 | |||
| f81a43381f | |||
| 44e97ead7b | |||
| 8507e683ae | |||
| 05cbd3d58c | |||
| 1de0b9c233 | |||
| b552a627e1 | |||
| 277b187a29 | |||
| 6762447ae7 | |||
| 41e4ad5d1d | |||
| e400aa0205 | |||
| 393c4987c8 | |||
| 47492e77eb | |||
| fe9ccb27b1 | |||
| 25f799bb8e | |||
| 413a432b13 | |||
| 2571453a54 | |||
| 0c06f15ea9 | |||
| d658767236 | |||
| 361be2630f | |||
| 9a05283566 | |||
| a06bb77f6b | |||
| 3c6ba22931 | |||
| 339e6986cf | |||
| 0c1d70ac92 | |||
| 21af1bad2b | |||
| b9b90267c5 | |||
| ba4bf07db2 | |||
| 09a1691caf | |||
| 09283f4d6a | |||
| e2c784740a | |||
| 9ff1a54c8d | |||
| 61c104b392 | |||
| fb8efbed2d | |||
| 7f6c9ebaf7 | |||
| 53933ebf5b | |||
| 48b4a57980 | |||
| 50e859e6b1 | |||
| ba6fd511b6 | |||
| c32ebcb2c6 | |||
| bbb37309fc | |||
| df6b16c7f6 | |||
| ec6e83ca2d | |||
| cf70368f67 | |||
| 28af44d199 | |||
| 855abbd99e | |||
| f13c5d74aa | |||
| 6f703eaf4e | |||
| 689d49f3f8 | |||
| 87f9e14e9c | |||
| 7129dc8c6a | |||
| 20745fe1d7 | |||
| f6835e9490 | |||
| deef19ff19 | |||
| 6adbe24d4c | |||
| 79122ae662 | |||
| 8d7bb87e7e | |||
| 5560ef0521 | |||
| 53d9975947 | |||
| d9e054477b | |||
| a93375882a | |||
| dafdb79db2 | |||
| 0fa10e4dd7 | |||
| f1d6f68625 | |||
| 757c29e1ae | |||
| 1989df2fac | |||
| 65a40149d8 | |||
| adb5fe1fa9 | |||
| c7bc8ee00f | |||
| dcd0807d7e | |||
| 013166a34e | |||
| 6ceffe68d0 | |||
| c26d893a77 | |||
| a3dde4e7fe | |||
| e4472d0d46 | |||
| f3c1992b10 | |||
| 12f72fb8e2 | |||
| 2f19eed023 | |||
| dfded79673 | |||
| b013a00095 | |||
| 7f813d23ce | |||
| 59b3d06417 | |||
| b0260dadba | |||
| fcf8efb826 | |||
| 5f2a5e5bad | |||
| f467642a1c | |||
| da079d47d7 | |||
| 5c9afcc785 | |||
| c0bfbc12c7 | |||
| aeb2fff068 | |||
| 2f9379a139 | |||
| 368a956b96 | |||
| 71ead9adea | |||
| 978e0eb986 | |||
| 341c0da987 | |||
| 84560ccd55 | |||
| ef93a715e1 | |||
| 41d99c3b70 | |||
| d4d57ed7fe | |||
| d2a564b9c7 | |||
| da6863f427 | |||
| 6abffe0ede | |||
| 23c2d02682 | |||
| e8d888c45b | |||
| c4e5ce685e | |||
| c8a04d25c9 | |||
| 012535a84e | |||
| 3fe9bbe665 | |||
| 29ac3da7b8 | |||
| 80d6953862 | |||
| 6c33a5c1f3 |
@@ -0,0 +1,3 @@
|
||||
((c++-mode (flycheck-gcc-language-standard . "c++11")
|
||||
(flycheck-clang-language-standard . "c++11")
|
||||
))
|
||||
@@ -0,0 +1,24 @@
|
||||
.git
|
||||
.DS_Store
|
||||
notebooks
|
||||
phone
|
||||
massivemap
|
||||
neos
|
||||
installer
|
||||
chffr/app2
|
||||
chffr/backend/env
|
||||
selfdrive/nav
|
||||
selfdrive/baseui
|
||||
chffr/lib/vidindex/vidindex
|
||||
chffr/lib/index_log/index_log
|
||||
selfdrive/test/simulator2
|
||||
**/cache_data
|
||||
xx/chffr/lib/index_log/index_log
|
||||
xx/chffr/lib/vidindex/vidindex
|
||||
xx/plus
|
||||
xx/community
|
||||
xx/projects
|
||||
!xx/projects/eon_testing_master
|
||||
!xx/projects/map3d
|
||||
xx/ops
|
||||
xx/junk
|
||||
@@ -0,0 +1,11 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{*.py, *.pyx, *pxd}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,27 @@
|
||||
*.keras filter=lfs diff=lfs merge=lfs -text
|
||||
*.dlc filter=lfs diff=lfs merge=lfs -text
|
||||
*.pb filter=lfs diff=lfs merge=lfs -text
|
||||
*.bin filter=lfs diff=lfs merge=lfs -text
|
||||
*.apk filter=lfs diff=lfs merge=lfs -text
|
||||
*.ipynb filter=nbstripout -diff
|
||||
external/ffmpeg/bin/ffmpeg_cuda filter=lfs diff=lfs merge=lfs -text
|
||||
models/segnet.keras filter=lfs diff=lfs merge=lfs -text
|
||||
external/zmq/lib/libzmq.a filter=lfs diff=lfs merge=lfs -text
|
||||
external/zmq/lib/libczmq.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/x64/lib/libacado_toolkit.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/x64/lib/libacado_toolkit_s.so.1.2.2beta filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/x64/lib/libacado_casadi.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/x64/lib/libacado_csparse.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/x64/lib/libacado_qpoases.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/aarch64/lib/libacado_toolkit.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/aarch64/lib/libacado_toolkit_s.so.1.2.2beta filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/aarch64/lib/libacado_casadi.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/aarch64/lib/libacado_csparse.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/acado/aarch64/lib/libacado_qpoases.a filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/fastcv/aarch64/libfastcvopt.so filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/fastcv/aarch64/libfastcvadsp_stub.so filter=lfs diff=lfs merge=lfs -text
|
||||
external/mac/MP4Box filter=lfs diff=lfs merge=lfs -text
|
||||
models/segnet2.keras filter=lfs diff=lfs merge=lfs -text
|
||||
external/opencl/*.deb filter=lfs diff=lfs merge=lfs -text
|
||||
phonelibs/zmq/aarch64-linux/lib/libzmq.a filter=lfs diff=lfs merge=lfs -text
|
||||
external/azcopy/azcopy filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -2,7 +2,7 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve openpilot
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
Choose one of the templates below:
|
||||
|
||||
# Fingerprint
|
||||
This pull requests adds a fingerprint for <Make - Model - Year - Trim>.
|
||||
|
||||
This is an explorer link to a drive with the stock system enabled: ...
|
||||
|
||||
# Car support
|
||||
This pull requests adds support for <Make - Model - Year - Trim>.
|
||||
|
||||
This is an explorer link to a drive with the stock system enabled: ...
|
||||
This is an explorer link to a drive with openpilot system enabled: ...
|
||||
|
||||
# Feature
|
||||
This pull requests adds feature X
|
||||
|
||||
## Description
|
||||
Explain what the feature does
|
||||
|
||||
## Testing
|
||||
Explain how the feature was tested. Either by the added unit tests, or what tests were performed while driving.
|
||||
@@ -0,0 +1,125 @@
|
||||
name: Openpilot Tests
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
RUN: docker run --shm-size 1G --rm tmppilot /bin/sh -c
|
||||
LOAD: docker load -i tmppilot.tar.gz/tmppilot.tar.gz
|
||||
UNIT_TEST: cd /tmp/openpilot && python -m unittest discover
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout submodules
|
||||
run: |
|
||||
git submodule update --init
|
||||
- run: |
|
||||
docker pull $(grep -ioP '(?<=^from)\s+\S+' Dockerfile.openpilot) || true
|
||||
docker pull docker.io/commaai/openpilot:latest || true
|
||||
|
||||
docker build --cache-from docker.io/commaai/openpilot:latest -t tmppilot -f Dockerfile.openpilot .
|
||||
docker save tmppilot:latest | gzip > tmppilot.tar.gz
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: tmppilot.tar.gz
|
||||
path: tmppilot.tar.gz
|
||||
|
||||
linter:
|
||||
name: linter
|
||||
runs-on: ubuntu-16.04
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: tmppilot.tar.gz
|
||||
- name: Load image
|
||||
run: $LOAD
|
||||
- name: flake8
|
||||
run: $RUN "cd /tmp/openpilot/ && ./flake8_openpilot.sh"
|
||||
- name: pylint
|
||||
run: $RUN "cd /tmp/openpilot/ && ./pylint_openpilot.sh"
|
||||
|
||||
unit_tests:
|
||||
name: unit tests
|
||||
runs-on: ubuntu-16.04
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: tmppilot.tar.gz
|
||||
- name: Load image
|
||||
run: $LOAD
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
$RUN cd /tmp/openpilot/selfdrive/test/test_fingerprints.py
|
||||
$RUN "$UNIT_TEST common"
|
||||
$RUN "$UNIT_TEST opendbc/can"
|
||||
$RUN "$UNIT_TEST selfdrive/boardd"
|
||||
$RUN "$UNIT_TEST selfdrive/controls"
|
||||
$RUN "$UNIT_TEST selfdrive/loggerd"
|
||||
$RUN "$UNIT_TEST selfdrive/car"
|
||||
$RUN "$UNIT_TEST selfdrive/locationd"
|
||||
$RUN "$UNIT_TEST selfdrive/athena"
|
||||
|
||||
process_replay:
|
||||
name: process replay
|
||||
runs-on: ubuntu-16.04
|
||||
needs: build
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: tmppilot.tar.gz
|
||||
- name: Load image
|
||||
run: $LOAD
|
||||
- name: Run replay
|
||||
run: |
|
||||
CONTAINER_NAME="tmppilot_${GITHUB_SHA}"
|
||||
docker run --shm-size 1G --name ${CONTAINER_NAME} tmppilot /bin/sh -c "cd /tmp/openpilot/selfdrive/test/process_replay && CI=1 ./test_processes.py"
|
||||
docker cp $CONTAINER_NAME:/tmp/openpilot/selfdrive/test/process_replay/diff.txt diff.txt
|
||||
docker rm $CONTAINER_NAME
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: always()
|
||||
with:
|
||||
name: process_replay_diff.txt
|
||||
path: diff.txt
|
||||
|
||||
test_longitudinal:
|
||||
name: longitudinal
|
||||
runs-on: ubuntu-16.04
|
||||
needs: build
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: tmppilot.tar.gz
|
||||
- name: Load image
|
||||
run: $LOAD
|
||||
- name: Test longitudinal
|
||||
run: |
|
||||
CONTAINER_NAME="tmppilot_${GITHUB_SHA}"
|
||||
docker run --shm-size 1G --name ${CONTAINER_NAME} tmppilot /bin/sh -c "cd /tmp/openpilot/selfdrive/test/longitudinal_maneuvers && OPTEST=1 ./test_longitudinal.py"
|
||||
mkdir out
|
||||
docker cp $CONTAINER_NAME:/tmp/openpilot/selfdrive/test/longitudinal_maneuvers/out/longitudinal/ out/
|
||||
docker rm $CONTAINER_NAME
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: always()
|
||||
with:
|
||||
name: longitudinal
|
||||
path: out
|
||||
|
||||
test_car_models:
|
||||
name: test car models
|
||||
runs-on: ubuntu-16.04
|
||||
needs: build
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: tmppilot.tar.gz
|
||||
- name: Load image
|
||||
run: $LOAD
|
||||
- name: Test car models
|
||||
run: $RUN "mkdir -p /data/params && cd /tmp/openpilot/selfdrive/test/ && ./test_car_models.py"
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
[submodule "panda"]
|
||||
path = panda
|
||||
url = ../../commaai/panda.git
|
||||
[submodule "opendbc"]
|
||||
path = opendbc
|
||||
url = ../../commaai/opendbc.git
|
||||
[submodule "laika_repo"]
|
||||
path = laika_repo
|
||||
url = ../../commaai/laika.git
|
||||
[submodule "apks"]
|
||||
path = apks
|
||||
url = ../../commaai/openpilot-apks.git
|
||||
[submodule "cereal"]
|
||||
path = cereal
|
||||
url = ../../commaai/cereal.git
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
FROM ubuntu:16.04
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
autoconf \
|
||||
build-essential \
|
||||
bzip2 \
|
||||
clang \
|
||||
cmake \
|
||||
curl \
|
||||
ffmpeg \
|
||||
git \
|
||||
libarchive-dev \
|
||||
libbz2-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libeigen3-dev \
|
||||
libffi-dev \
|
||||
libglew-dev \
|
||||
libgles2-mesa-dev \
|
||||
libglfw3-dev \
|
||||
libglib2.0-0 \
|
||||
liblzma-dev \
|
||||
libmysqlclient-dev \
|
||||
libomp-dev \
|
||||
libopencv-dev \
|
||||
libssl-dev \
|
||||
libsqlite3-dev \
|
||||
libtool \
|
||||
libusb-1.0-0-dev \
|
||||
libzmq5-dev \
|
||||
locales \
|
||||
ocl-icd-libopencl1 \
|
||||
ocl-icd-opencl-dev \
|
||||
opencl-headers \
|
||||
python-dev \
|
||||
python-pip \
|
||||
screen \
|
||||
sudo \
|
||||
vim \
|
||||
wget
|
||||
|
||||
|
||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US:en
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
|
||||
RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
|
||||
|
||||
ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}"
|
||||
RUN pyenv install 3.7.3
|
||||
RUN pyenv global 3.7.3
|
||||
RUN pyenv rehash
|
||||
|
||||
RUN pip install pipenv==2018.11.26
|
||||
|
||||
COPY Pipfile /tmp/
|
||||
COPY Pipfile.lock /tmp/
|
||||
|
||||
RUN python --version
|
||||
RUN cd /tmp && pipenv install --system --deploy
|
||||
|
||||
# Install subset of dev dependencies needed for CI
|
||||
RUN pip install matplotlib==3.1.1 dictdiffer==0.8.0 fastcluster==1.1.25 aenum==2.2.1 scipy==1.3.1 lru-dict==1.1.6 tenacity==5.1.1 azure-common==1.1.23 azure-nspkg==3.0.2 azure-storage-blob==2.1.0 azure-storage-common==2.1.0 azure-storage-nspkg==3.1.0 pycurl==7.43.0.3
|
||||
|
||||
ENV PATH="/tmp/openpilot/external/bin:${PATH}"
|
||||
ENV PYTHONPATH /tmp/openpilot:${PYTHONPATH}
|
||||
|
||||
RUN mkdir -p /tmp/openpilot
|
||||
COPY ./flake8_openpilot.sh /tmp/openpilot/
|
||||
COPY ./pylint_openpilot.sh /tmp/openpilot/
|
||||
COPY ./.pylintrc /tmp/openpilot/
|
||||
|
||||
COPY ./release /tmp/openpilot/release
|
||||
COPY ./common /tmp/openpilot/common
|
||||
COPY ./cereal /tmp/openpilot/cereal
|
||||
COPY ./opendbc /tmp/openpilot/opendbc
|
||||
COPY ./selfdrive /tmp/openpilot/selfdrive
|
||||
COPY ./phonelibs /tmp/openpilot/phonelibs
|
||||
COPY ./pyextra /tmp/openpilot/pyextra
|
||||
COPY ./panda /tmp/openpilot/panda
|
||||
COPY ./external /tmp/openpilot/external
|
||||
COPY ./tools /tmp/openpilot/tools
|
||||
|
||||
COPY SConstruct /tmp/openpilot/SConstruct
|
||||
|
||||
RUN mkdir -p /tmp/openpilot/selfdrive/test/out
|
||||
RUN cd /tmp/openpilot && scons -j$(nproc)
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
image 'python:3.7.3'
|
||||
args '--user=root'
|
||||
}
|
||||
|
||||
}
|
||||
stages {
|
||||
stage('EON Build/Test') {
|
||||
steps {
|
||||
lock(resource: "", label: 'eon', inversePrecedence: true, variable: 'eon_name', quantity: 1){
|
||||
timeout(time: 30, unit: 'MINUTES') {
|
||||
dir(path: 'release') {
|
||||
sh 'pip install paramiko'
|
||||
sh 'python remote_build.py'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
opencv-python= "==3.4.2.17"
|
||||
PyQt5 = "*"
|
||||
ipython = "*"
|
||||
networkx = "==2.3"
|
||||
azure-core = "==1.1.1"
|
||||
azure-common = "==1.1.24"
|
||||
azure-nspkg = "==3.0.2"
|
||||
azure-storage-blob = "==2.1.0"
|
||||
azure-storage-common = "==2.1.0"
|
||||
azure-storage-nspkg = "==3.1.0"
|
||||
boto = "*"
|
||||
"boto3" = "*"
|
||||
control = "*"
|
||||
datadog = "*"
|
||||
dlib = "*"
|
||||
elasticsearch = "*"
|
||||
future = "*"
|
||||
futures = "*"
|
||||
pycocotools = {git = "https://github.com/cocodataset/cocoapi.git",subdirectory = "PythonAPI"}
|
||||
gunicorn = "*"
|
||||
"h5py" = "*"
|
||||
hexdump = "*"
|
||||
"html5lib" = "*"
|
||||
imageio = "*"
|
||||
ipykernel = "*"
|
||||
joblib = "*"
|
||||
json-logging-py = "*"
|
||||
jupyter = "*"
|
||||
libarchive = "*"
|
||||
lru-dict = "*"
|
||||
"mpld3" = "*"
|
||||
msgpack-python = "*"
|
||||
nbstripout = "*"
|
||||
numpy = "*"
|
||||
osmium = "*"
|
||||
pbr = "*"
|
||||
percache = "*"
|
||||
pprofile = "*"
|
||||
pycurl = "*"
|
||||
git-pylint-commit-hook = "*"
|
||||
pymongo = "*"
|
||||
"pynmea2" = "*"
|
||||
pypolyline = "*"
|
||||
python-logstash = "*"
|
||||
redis = "*"
|
||||
"s2sphere" = "*"
|
||||
scikit-image = "*"
|
||||
"subprocess32" = "*"
|
||||
tenacity = "*"
|
||||
tensorflow-gpu = "==2.0"
|
||||
PyJWT = "==1.4.1"
|
||||
PyMySQL = "==0.9.2"
|
||||
Werkzeug = "*"
|
||||
"backports.lzma" = "*"
|
||||
Flask-Cors = "*"
|
||||
Flask-SocketIO = "*"
|
||||
"GeoAlchemy2" = "*"
|
||||
Pygments = "*"
|
||||
PyNaCl = "*"
|
||||
"PySDL2" = "*"
|
||||
reverse_geocoder = "*"
|
||||
Shapely = "*"
|
||||
SQLAlchemy = "*"
|
||||
scipy = "*"
|
||||
fastcluster = "*"
|
||||
backports-abc = "*"
|
||||
pygame = "*"
|
||||
simplejson = "*"
|
||||
python-logstash-async = "*"
|
||||
seaborn = "*"
|
||||
pyproj = "*"
|
||||
mock = "*"
|
||||
matplotlib = "*"
|
||||
dictdiffer = "*"
|
||||
aenum = "*"
|
||||
coverage = "*"
|
||||
azure-cli-core = "*"
|
||||
paramiko = "*"
|
||||
aiohttp = "*"
|
||||
|
||||
[packages]
|
||||
overpy = {git = "https://github.com/commaai/python-overpy.git",ref = "f86529af402d4642e1faeb146671c40284007323"}
|
||||
atomicwrites = "*"
|
||||
cffi = "*"
|
||||
crcmod = "*"
|
||||
hexdump = "*"
|
||||
libusb1 = "*"
|
||||
numpy = "*"
|
||||
psutil = "*"
|
||||
pycapnp = "*"
|
||||
cryptography = "*"
|
||||
pyserial = "*"
|
||||
python-dateutil = "*"
|
||||
pyzmq = "*"
|
||||
raven = "*"
|
||||
requests = "*"
|
||||
setproctitle = "*"
|
||||
six = "*"
|
||||
smbus2 = "*"
|
||||
sympy = "*"
|
||||
tqdm = "*"
|
||||
Cython = "*"
|
||||
PyYAML = "*"
|
||||
websocket_client = "*"
|
||||
Logentries = {git = "https://github.com/commaai/le_python.git",ref = "feaeacb48f7f4bdb02c0a8fc092326d4e101b7f2"}
|
||||
urllib3 = "*"
|
||||
chardet = "*"
|
||||
idna = "*"
|
||||
gunicorn = "*"
|
||||
utm = "*"
|
||||
json-rpc = "*"
|
||||
Flask = "*"
|
||||
PyJWT = "*"
|
||||
"Jinja2" = "*"
|
||||
nose = "*"
|
||||
flake8 = "*"
|
||||
pylint = "*"
|
||||
pycryptodome = "*"
|
||||
pillow = "*"
|
||||
scons = "*"
|
||||
cysignals = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7.3"
|
||||
Generated
+2970
File diff suppressed because it is too large
Load Diff
@@ -96,7 +96,6 @@ Supported Cars
|
||||
| Lexus | ES Hybrid 2019 | All | openpilot | 0mph | 0mph |
|
||||
| Lexus | IS 2017-2019 | All | Stock | 22mph | 0mph |
|
||||
| Lexus | IS Hybrid 2017 | All | Stock | 0mph | 0mph |
|
||||
| Lexus | NX Hybrid 2018 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Lexus | RX 2016-17 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Lexus | RX 2020 | All | openpilot | 0mph | 0mph |
|
||||
| Lexus | RX Hybrid 2016-19 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
@@ -114,7 +113,6 @@ Supported Cars
|
||||
| Toyota | Corolla Hybrid 2020 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Highlander 2017-19 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Toyota | Highlander Hybrid 2017-19 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Toyota | Highlander 2020 | All | openpilot | 0mph | 0mph |
|
||||
| Toyota | Prius 2016 | TSS-P | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Toyota | Prius 2017-19 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
| Toyota | Prius Prime 2017-20 | All | Stock<sup>4</sup>| 0mph | 0mph |
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
Version 0.7.3 (2020-02-21)
|
||||
========================
|
||||
* Support for 2020 Highlander thanks to che220!
|
||||
* Support for 2018 Lexus NX 300h thanks to kengggg!
|
||||
* Speed up ECU firmware query
|
||||
* Fix bug where manager would sometimes hang after shutting down the car
|
||||
|
||||
Version 0.7.2 (2020-02-07)
|
||||
========================
|
||||
* ECU firmware version based fingerprinting for Honda & Toyota
|
||||
|
||||
@@ -228,6 +228,5 @@ if arch == "aarch64":
|
||||
SConscript(['selfdrive/clocksd/SConscript'])
|
||||
|
||||
SConscript(['selfdrive/locationd/SConscript'])
|
||||
SConscript(['selfdrive/locationd/kalman/SConscript'])
|
||||
|
||||
# TODO: finish cereal, dbcbuilder, MPC
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Place apks here that you want to be installed with the name <app>.apk
|
||||
|
||||
These won't be committed into one, but for now baseui will ship with openpilot.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Submodule
+1
Submodule apks added at d4f4f07a15
Submodule
+1
Submodule cereal added at ab32956aaf
@@ -1 +0,0 @@
|
||||
.sconsign.dblite
|
||||
Vendored
-23
@@ -1,23 +0,0 @@
|
||||
name: Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
||||
runs-on: ubuntu-16.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build docker image
|
||||
run: docker build -t cereal .
|
||||
- name: Unit Tests
|
||||
run: |
|
||||
docker run cereal bash -c "scons --test --asan -j$(nproc) && messaging/test_runner"
|
||||
- name: Test ZMQ
|
||||
run: |
|
||||
docker run cereal bash -c "ZMQ=1 python -m unittest discover ."
|
||||
- name: Test MSGQ
|
||||
run: |
|
||||
docker run cereal bash -c "MSGQ=1 python -m unittest discover ."
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
gen
|
||||
node_modules
|
||||
package-lock.json
|
||||
*.pyc
|
||||
__pycache__
|
||||
.*.swp
|
||||
.*.swo
|
||||
libcereal*.a
|
||||
libmessaging.*
|
||||
libmessaging_shared.*
|
||||
services.h
|
||||
.sconsign.dblite
|
||||
libcereal_shared.*
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
from ubuntu:16.04
|
||||
|
||||
RUN apt-get update && apt-get install -y libzmq3-dev clang wget git autoconf libtool curl make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev python-openssl
|
||||
|
||||
RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
|
||||
ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}"
|
||||
RUN pyenv install 3.7.3
|
||||
RUN pyenv global 3.7.3
|
||||
RUN pyenv rehash
|
||||
RUN pip3 install pyyaml==5.1.2 Cython==0.29.14 scons==3.1.1 pycapnp==0.6.4
|
||||
|
||||
WORKDIR /project/cereal
|
||||
COPY install_capnp.sh .
|
||||
RUN ./install_capnp.sh
|
||||
|
||||
ENV PYTHONPATH=/project
|
||||
|
||||
COPY . .
|
||||
RUN scons -c && scons -j$(nproc)
|
||||
@@ -1,42 +0,0 @@
|
||||
What is cereal?
|
||||
----
|
||||
|
||||
cereal is both a messaging spec for robotics systems as well as generic high performance IPC pub sub messaging with a single publisher and multiple subscribers.
|
||||
|
||||
Imagine this use case:
|
||||
* A sensor process reads gyro measurements directly from an IMU and publishes a sensorEvents packet
|
||||
* A calibration process subscribes to the sensorEvents packet to use the IMU
|
||||
* A localization process subscribes to the sensorEvents packet to use the IMU also
|
||||
|
||||
|
||||
Messaging Spec
|
||||
----
|
||||
|
||||
You'll find the message types in [log.capnp](log.capnp). It uses [Cap'n proto](https://capnproto.org/capnp-tool.html) and defines one struct called Event.
|
||||
|
||||
All Events have a logMonoTime and a valid. Then a big union defines the packet type.
|
||||
|
||||
|
||||
Pub Sub Backends
|
||||
----
|
||||
|
||||
cereal supports two backends, one based on [zmq](https://zeromq.org/), the other called msgq, a custom pub sub based on shared memory that doesn't require the bytes to pass through the kernel.
|
||||
|
||||
Example
|
||||
---
|
||||
```python
|
||||
import cereal.messaging as messaging
|
||||
|
||||
# in subscriber
|
||||
sm = messaging.SubMaster(['sensorEvents'])
|
||||
while 1:
|
||||
sm.update()
|
||||
print(sm['sensorEvents'])
|
||||
|
||||
# in publisher
|
||||
pm = messaging.PubMaster(['sensorEvents'])
|
||||
dat = messaging.new_message()
|
||||
dat.init('sensorEvents', 1)
|
||||
dat.sensorEvents[0] = {"gyro": {"v": [0.1, -0.1, 0.1]}}
|
||||
pm.send('sensorEvents', dat)
|
||||
```
|
||||
@@ -1,68 +0,0 @@
|
||||
Import('env', 'arch', 'zmq')
|
||||
|
||||
gen_dir = Dir('gen')
|
||||
messaging_dir = Dir('messaging')
|
||||
|
||||
# TODO: remove src-prefix and cereal from command string. can we set working directory?
|
||||
env.Command(["gen/c/include/c++.capnp.h", "gen/c/include/java.capnp.h"], [], "mkdir -p " + gen_dir.path + "/c/include && touch $TARGETS")
|
||||
env.Command(
|
||||
['gen/c/car.capnp.c', 'gen/c/log.capnp.c', 'gen/c/car.capnp.h', 'gen/c/log.capnp.h'],
|
||||
['car.capnp', 'log.capnp'],
|
||||
'capnpc $SOURCES --src-prefix=cereal -o c:' + gen_dir.path + '/c/')
|
||||
env.Command(
|
||||
['gen/cpp/car.capnp.c++', 'gen/cpp/log.capnp.c++', 'gen/cpp/car.capnp.h', 'gen/cpp/log.capnp.h'],
|
||||
['car.capnp', 'log.capnp'],
|
||||
'capnpc $SOURCES --src-prefix=cereal -o c++:' + gen_dir.path + '/cpp/')
|
||||
import shutil
|
||||
if shutil.which('capnpc-java'):
|
||||
env.Command(
|
||||
['gen/java/Car.java', 'gen/java/Log.java'],
|
||||
['car.capnp', 'log.capnp'],
|
||||
'capnpc $SOURCES --src-prefix=cereal -o java:' + gen_dir.path + '/java/')
|
||||
|
||||
# TODO: remove non shared cereal and messaging
|
||||
cereal_objects = env.SharedObject([
|
||||
'gen/c/car.capnp.c',
|
||||
'gen/c/log.capnp.c',
|
||||
'gen/cpp/car.capnp.c++',
|
||||
'gen/cpp/log.capnp.c++',
|
||||
])
|
||||
|
||||
env.Library('cereal', cereal_objects)
|
||||
env.SharedLibrary('cereal_shared', cereal_objects, LIBS=["capnp_c"])
|
||||
|
||||
cereal_dir = Dir('.')
|
||||
services_h = env.Command(
|
||||
['services.h'],
|
||||
['service_list.yaml', 'services.py'],
|
||||
'python3 ' + cereal_dir.path + '/services.py > $TARGET')
|
||||
|
||||
messaging_objects = env.SharedObject([
|
||||
'messaging/messaging.cc',
|
||||
'messaging/impl_zmq.cc',
|
||||
'messaging/impl_msgq.cc',
|
||||
'messaging/msgq.cc',
|
||||
])
|
||||
|
||||
messaging_lib = env.Library('messaging', messaging_objects)
|
||||
Depends('messaging/impl_zmq.cc', services_h)
|
||||
|
||||
# note, this rebuilds the deps shared, zmq is statically linked to make APK happy
|
||||
# TODO: get APK to load system zmq to remove the static link
|
||||
shared_lib_shared_lib = [zmq, 'm', 'stdc++'] + ["gnustl_shared"] if arch == "aarch64" else [zmq]
|
||||
env.SharedLibrary('messaging_shared', messaging_objects, LIBS=shared_lib_shared_lib)
|
||||
|
||||
env.Program('messaging/bridge', ['messaging/bridge.cc'], LIBS=[messaging_lib, 'zmq'])
|
||||
Depends('messaging/bridge.cc', services_h)
|
||||
|
||||
# different target?
|
||||
#env.Program('messaging/demo', ['messaging/demo.cc'], LIBS=[messaging_lib, 'zmq'])
|
||||
|
||||
|
||||
env.Command(['messaging/messaging_pyx.so'],
|
||||
[messaging_lib, 'messaging/messaging_pyx_setup.py', 'messaging/messaging_pyx.pyx', 'messaging/messaging.pxd'],
|
||||
"cd " + messaging_dir.path + " && python3 messaging_pyx_setup.py build_ext --inplace")
|
||||
|
||||
|
||||
if GetOption('test'):
|
||||
env.Program('messaging/test_runner', ['messaging/test_runner.cc', 'messaging/msgq_tests.cc'], LIBS=[messaging_lib])
|
||||
@@ -1,49 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
zmq = 'zmq'
|
||||
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
||||
|
||||
cereal_dir = Dir('.')
|
||||
|
||||
cpppath = [
|
||||
cereal_dir,
|
||||
'/usr/lib/include',
|
||||
]
|
||||
|
||||
AddOption('--test',
|
||||
action='store_true',
|
||||
help='build test files')
|
||||
|
||||
AddOption('--asan',
|
||||
action='store_true',
|
||||
help='turn on ASAN')
|
||||
|
||||
ccflags_asan = ["-fsanitize=address", "-fno-omit-frame-pointer"] if GetOption('asan') else []
|
||||
ldflags_asan = ["-fsanitize=address"] if GetOption('asan') else []
|
||||
|
||||
env = Environment(
|
||||
ENV=os.environ,
|
||||
CC='clang',
|
||||
CXX='clang++',
|
||||
CCFLAGS=[
|
||||
"-g",
|
||||
"-fPIC",
|
||||
"-O2",
|
||||
"-Werror=implicit-function-declaration",
|
||||
"-Werror=incompatible-pointer-types",
|
||||
"-Werror=int-conversion",
|
||||
"-Werror=return-type",
|
||||
"-Werror=format-extra-args",
|
||||
] + ccflags_asan,
|
||||
LDFLAGS=ldflags_asan,
|
||||
LINKFLAGS=ldflags_asan,
|
||||
|
||||
CFLAGS="-std=gnu11",
|
||||
CXXFLAGS="-std=c++14",
|
||||
CPPPATH=cpppath,
|
||||
)
|
||||
|
||||
|
||||
Export('env', 'zmq', 'arch')
|
||||
SConscript(['SConscript'])
|
||||
@@ -1,8 +0,0 @@
|
||||
import os
|
||||
import capnp
|
||||
|
||||
CEREAL_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
capnp.remove_import_hook()
|
||||
|
||||
log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp"))
|
||||
car = capnp.load(os.path.join(CEREAL_PATH, "car.capnp"))
|
||||
@@ -1,482 +0,0 @@
|
||||
using Cxx = import "./include/c++.capnp";
|
||||
$Cxx.namespace("cereal");
|
||||
|
||||
using Java = import "./include/java.capnp";
|
||||
$Java.package("ai.comma.openpilot.cereal");
|
||||
$Java.outerClassname("Car");
|
||||
|
||||
@0x8e2af1e708af8b8d;
|
||||
|
||||
# ******* events causing controls state machine transition *******
|
||||
|
||||
struct CarEvent @0x9b1657f34caf3ad3 {
|
||||
name @0 :EventName;
|
||||
enable @1 :Bool;
|
||||
noEntry @2 :Bool;
|
||||
warning @3 :Bool;
|
||||
userDisable @4 :Bool;
|
||||
softDisable @5 :Bool;
|
||||
immediateDisable @6 :Bool;
|
||||
preEnable @7 :Bool;
|
||||
permanent @8 :Bool;
|
||||
|
||||
enum EventName @0xbaa8c5d505f727de {
|
||||
# TODO: copy from error list
|
||||
canError @0;
|
||||
steerUnavailable @1;
|
||||
brakeUnavailable @2;
|
||||
gasUnavailable @3;
|
||||
wrongGear @4;
|
||||
doorOpen @5;
|
||||
seatbeltNotLatched @6;
|
||||
espDisabled @7;
|
||||
wrongCarMode @8;
|
||||
steerTempUnavailable @9;
|
||||
reverseGear @10;
|
||||
buttonCancel @11;
|
||||
buttonEnable @12;
|
||||
pedalPressed @13;
|
||||
cruiseDisabled @14;
|
||||
radarCanError @15;
|
||||
dataNeeded @16;
|
||||
speedTooLow @17;
|
||||
outOfSpace @18;
|
||||
overheat @19;
|
||||
calibrationIncomplete @20;
|
||||
calibrationInvalid @21;
|
||||
controlsMismatch @22;
|
||||
pcmEnable @23;
|
||||
pcmDisable @24;
|
||||
noTarget @25;
|
||||
radarFault @26;
|
||||
modelCommIssueDEPRECATED @27;
|
||||
brakeHold @28;
|
||||
parkBrake @29;
|
||||
manualRestart @30;
|
||||
lowSpeedLockout @31;
|
||||
plannerError @32;
|
||||
ipasOverride @33;
|
||||
debugAlert @34;
|
||||
steerTempUnavailableMute @35;
|
||||
resumeRequired @36;
|
||||
preDriverDistracted @37;
|
||||
promptDriverDistracted @38;
|
||||
driverDistracted @39;
|
||||
geofence @40;
|
||||
driverMonitorOn @41;
|
||||
driverMonitorOff @42;
|
||||
preDriverUnresponsive @43;
|
||||
promptDriverUnresponsive @44;
|
||||
driverUnresponsive @45;
|
||||
belowSteerSpeed @46;
|
||||
calibrationProgress @47;
|
||||
lowBattery @48;
|
||||
invalidGiraffeHonda @49;
|
||||
vehicleModelInvalid @50;
|
||||
controlsFailed @51;
|
||||
sensorDataInvalid @52;
|
||||
commIssue @53;
|
||||
tooDistracted @54;
|
||||
posenetInvalid @55;
|
||||
soundsUnavailable @56;
|
||||
preLaneChangeLeft @57;
|
||||
preLaneChangeRight @58;
|
||||
laneChange @59;
|
||||
invalidGiraffeToyota @60;
|
||||
internetConnectivityNeeded @61;
|
||||
communityFeatureDisallowed @62;
|
||||
lowMemory @63;
|
||||
stockAeb @64;
|
||||
ldw @65;
|
||||
carUnrecognized @66;
|
||||
radarCommIssue @67;
|
||||
driverMonitorLowAcc @68;
|
||||
}
|
||||
}
|
||||
|
||||
# ******* main car state @ 100hz *******
|
||||
# all speeds in m/s
|
||||
|
||||
struct CarState {
|
||||
errorsDEPRECATED @0 :List(CarEvent.EventName);
|
||||
events @13 :List(CarEvent);
|
||||
|
||||
# car speed
|
||||
vEgo @1 :Float32; # best estimate of speed
|
||||
aEgo @16 :Float32; # best estimate of acceleration
|
||||
vEgoRaw @17 :Float32; # unfiltered speed from CAN sensors
|
||||
yawRate @22 :Float32; # best estimate of yaw rate
|
||||
standstill @18 :Bool;
|
||||
wheelSpeeds @2 :WheelSpeeds;
|
||||
|
||||
# gas pedal, 0.0-1.0
|
||||
gas @3 :Float32; # this is user + computer
|
||||
gasPressed @4 :Bool; # this is user pedal only
|
||||
|
||||
# brake pedal, 0.0-1.0
|
||||
brake @5 :Float32; # this is user pedal only
|
||||
brakePressed @6 :Bool; # this is user pedal only
|
||||
brakeLights @19 :Bool;
|
||||
|
||||
# steering wheel
|
||||
steeringAngle @7 :Float32; # deg
|
||||
steeringRate @15 :Float32; # deg/s
|
||||
steeringTorque @8 :Float32; # TODO: standardize units
|
||||
steeringTorqueEps @27 :Float32; # TODO: standardize units
|
||||
steeringPressed @9 :Bool; # if the user is using the steering wheel
|
||||
steeringRateLimited @29 :Bool; # if the torque is limited by the rate limiter
|
||||
stockAeb @30 :Bool;
|
||||
stockFcw @31 :Bool;
|
||||
|
||||
# cruise state
|
||||
cruiseState @10 :CruiseState;
|
||||
|
||||
# gear
|
||||
gearShifter @14 :GearShifter;
|
||||
|
||||
# button presses
|
||||
buttonEvents @11 :List(ButtonEvent);
|
||||
leftBlinker @20 :Bool;
|
||||
rightBlinker @21 :Bool;
|
||||
genericToggle @23 :Bool;
|
||||
|
||||
# lock info
|
||||
doorOpen @24 :Bool;
|
||||
seatbeltUnlatched @25 :Bool;
|
||||
canValid @26 :Bool;
|
||||
|
||||
# clutch (manual transmission only)
|
||||
clutchPressed @28 :Bool;
|
||||
|
||||
# which packets this state came from
|
||||
canMonoTimes @12: List(UInt64);
|
||||
|
||||
struct WheelSpeeds {
|
||||
# optional wheel speeds
|
||||
fl @0 :Float32;
|
||||
fr @1 :Float32;
|
||||
rl @2 :Float32;
|
||||
rr @3 :Float32;
|
||||
}
|
||||
|
||||
struct CruiseState {
|
||||
enabled @0 :Bool;
|
||||
speed @1 :Float32;
|
||||
available @2 :Bool;
|
||||
speedOffset @3 :Float32;
|
||||
standstill @4 :Bool;
|
||||
}
|
||||
|
||||
enum GearShifter {
|
||||
unknown @0;
|
||||
park @1;
|
||||
drive @2;
|
||||
neutral @3;
|
||||
reverse @4;
|
||||
sport @5;
|
||||
low @6;
|
||||
brake @7;
|
||||
eco @8;
|
||||
manumatic @9;
|
||||
}
|
||||
|
||||
# send on change
|
||||
struct ButtonEvent {
|
||||
pressed @0 :Bool;
|
||||
type @1 :Type;
|
||||
|
||||
enum Type {
|
||||
unknown @0;
|
||||
leftBlinker @1;
|
||||
rightBlinker @2;
|
||||
accelCruise @3;
|
||||
decelCruise @4;
|
||||
cancel @5;
|
||||
altButton1 @6;
|
||||
altButton2 @7;
|
||||
altButton3 @8;
|
||||
setCruise @9;
|
||||
resumeCruise @10;
|
||||
gapAdjustCruise @11;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ******* radar state @ 20hz *******
|
||||
|
||||
struct RadarData @0x888ad6581cf0aacb {
|
||||
errors @0 :List(Error);
|
||||
points @1 :List(RadarPoint);
|
||||
|
||||
# which packets this state came from
|
||||
canMonoTimes @2 :List(UInt64);
|
||||
|
||||
enum Error {
|
||||
canError @0;
|
||||
fault @1;
|
||||
wrongConfig @2;
|
||||
}
|
||||
|
||||
# similar to LiveTracks
|
||||
# is one timestamp valid for all? I think so
|
||||
struct RadarPoint {
|
||||
trackId @0 :UInt64; # no trackId reuse
|
||||
|
||||
# these 3 are the minimum required
|
||||
dRel @1 :Float32; # m from the front bumper of the car
|
||||
yRel @2 :Float32; # m
|
||||
vRel @3 :Float32; # m/s
|
||||
|
||||
# these are optional and valid if they are not NaN
|
||||
aRel @4 :Float32; # m/s^2
|
||||
yvRel @5 :Float32; # m/s
|
||||
|
||||
# some radars flag measurements VS estimates
|
||||
measured @6 :Bool;
|
||||
}
|
||||
}
|
||||
|
||||
# ******* car controls @ 100hz *******
|
||||
|
||||
struct CarControl {
|
||||
# must be true for any actuator commands to work
|
||||
enabled @0 :Bool;
|
||||
active @7 :Bool;
|
||||
|
||||
gasDEPRECATED @1 :Float32;
|
||||
brakeDEPRECATED @2 :Float32;
|
||||
steeringTorqueDEPRECATED @3 :Float32;
|
||||
|
||||
actuators @6 :Actuators;
|
||||
|
||||
cruiseControl @4 :CruiseControl;
|
||||
hudControl @5 :HUDControl;
|
||||
|
||||
struct Actuators {
|
||||
# range from 0.0 - 1.0
|
||||
gas @0: Float32;
|
||||
brake @1: Float32;
|
||||
# range from -1.0 - 1.0
|
||||
steer @2: Float32;
|
||||
steerAngle @3: Float32;
|
||||
}
|
||||
|
||||
struct CruiseControl {
|
||||
cancel @0: Bool;
|
||||
override @1: Bool;
|
||||
speedOverride @2: Float32;
|
||||
accelOverride @3: Float32;
|
||||
}
|
||||
|
||||
struct HUDControl {
|
||||
speedVisible @0: Bool;
|
||||
setSpeed @1: Float32;
|
||||
lanesVisible @2: Bool;
|
||||
leadVisible @3: Bool;
|
||||
visualAlert @4: VisualAlert;
|
||||
audibleAlert @5: AudibleAlert;
|
||||
rightLaneVisible @6: Bool;
|
||||
leftLaneVisible @7: Bool;
|
||||
rightLaneDepart @8: Bool;
|
||||
leftLaneDepart @9: Bool;
|
||||
|
||||
enum VisualAlert {
|
||||
# these are the choices from the Honda
|
||||
# map as good as you can for your car
|
||||
none @0;
|
||||
fcw @1;
|
||||
steerRequired @2;
|
||||
brakePressed @3;
|
||||
wrongGear @4;
|
||||
seatbeltUnbuckled @5;
|
||||
speedTooHigh @6;
|
||||
ldw @7;
|
||||
}
|
||||
|
||||
enum AudibleAlert {
|
||||
# these are the choices from the Honda
|
||||
# map as good as you can for your car
|
||||
none @0;
|
||||
chimeEngage @1;
|
||||
chimeDisengage @2;
|
||||
chimeError @3;
|
||||
chimeWarning1 @4;
|
||||
chimeWarning2 @5;
|
||||
chimeWarningRepeat @6;
|
||||
chimePrompt @7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ****** car param ******
|
||||
|
||||
struct CarParams {
|
||||
carName @0 :Text;
|
||||
carFingerprint @1 :Text;
|
||||
|
||||
enableGasInterceptor @2 :Bool;
|
||||
enableCruise @3 :Bool;
|
||||
enableCamera @4 :Bool;
|
||||
enableDsu @5 :Bool; # driving support unit
|
||||
enableApgs @6 :Bool; # advanced parking guidance system
|
||||
|
||||
minEnableSpeed @7 :Float32;
|
||||
minSteerSpeed @8 :Float32;
|
||||
safetyModel @9 :SafetyModel;
|
||||
safetyModelPassive @42 :SafetyModel = silent;
|
||||
safetyParam @10 :Int16;
|
||||
|
||||
steerMaxBP @11 :List(Float32);
|
||||
steerMaxV @12 :List(Float32);
|
||||
gasMaxBP @13 :List(Float32);
|
||||
gasMaxV @14 :List(Float32);
|
||||
brakeMaxBP @15 :List(Float32);
|
||||
brakeMaxV @16 :List(Float32);
|
||||
|
||||
# things about the car in the manual
|
||||
mass @17 :Float32; # [kg] running weight
|
||||
wheelbase @18 :Float32; # [m] distance from rear to front axle
|
||||
centerToFront @19 :Float32; # [m] GC distance to front axle
|
||||
steerRatio @20 :Float32; # [] ratio between front wheels and steering wheel angles
|
||||
steerRatioRear @21 :Float32; # [] rear steering ratio wrt front steering (usually 0)
|
||||
|
||||
# things we can derive
|
||||
rotationalInertia @22 :Float32; # [kg*m2] body rotational inertia
|
||||
tireStiffnessFront @23 :Float32; # [N/rad] front tire coeff of stiff
|
||||
tireStiffnessRear @24 :Float32; # [N/rad] rear tire coeff of stiff
|
||||
|
||||
longitudinalTuning @25 :LongitudinalPIDTuning;
|
||||
lateralParams @48 :LateralParams;
|
||||
lateralTuning :union {
|
||||
pid @26 :LateralPIDTuning;
|
||||
indi @27 :LateralINDITuning;
|
||||
lqr @40 :LateralLQRTuning;
|
||||
}
|
||||
|
||||
steerLimitAlert @28 :Bool;
|
||||
steerLimitTimer @47 :Float32; # time before steerLimitAlert is issued
|
||||
|
||||
vEgoStopping @29 :Float32; # Speed at which the car goes into stopping state
|
||||
directAccelControl @30 :Bool; # Does the car have direct accel control or just gas/brake
|
||||
stoppingControl @31 :Bool; # Does the car allows full control even at lows speeds when stopping
|
||||
startAccel @32 :Float32; # Required acceleraton to overcome creep braking
|
||||
steerRateCost @33 :Float32; # Lateral MPC cost on steering rate
|
||||
steerControlType @34 :SteerControlType;
|
||||
radarOffCan @35 :Bool; # True when radar objects aren't visible on CAN
|
||||
|
||||
steerActuatorDelay @36 :Float32; # Steering wheel actuator delay in seconds
|
||||
openpilotLongitudinalControl @37 :Bool; # is openpilot doing the longitudinal control?
|
||||
carVin @38 :Text; # VIN number queried during fingerprinting
|
||||
isPandaBlack @39: Bool;
|
||||
dashcamOnly @41: Bool;
|
||||
transmissionType @43 :TransmissionType;
|
||||
carFw @44 :List(CarFw);
|
||||
radarTimeStep @45: Float32 = 0.05; # time delta between radar updates, 20Hz is very standard
|
||||
communityFeature @46: Bool; # true if a community maintained feature is detected
|
||||
fingerprintSource @49: FingerprintSource;
|
||||
|
||||
struct LateralParams {
|
||||
torqueBP @0 :List(Int32);
|
||||
torqueV @1 :List(Int32);
|
||||
}
|
||||
|
||||
struct LateralPIDTuning {
|
||||
kpBP @0 :List(Float32);
|
||||
kpV @1 :List(Float32);
|
||||
kiBP @2 :List(Float32);
|
||||
kiV @3 :List(Float32);
|
||||
kf @4 :Float32;
|
||||
}
|
||||
|
||||
struct LongitudinalPIDTuning {
|
||||
kpBP @0 :List(Float32);
|
||||
kpV @1 :List(Float32);
|
||||
kiBP @2 :List(Float32);
|
||||
kiV @3 :List(Float32);
|
||||
deadzoneBP @4 :List(Float32);
|
||||
deadzoneV @5 :List(Float32);
|
||||
}
|
||||
|
||||
struct LateralINDITuning {
|
||||
outerLoopGain @0 :Float32;
|
||||
innerLoopGain @1 :Float32;
|
||||
timeConstant @2 :Float32;
|
||||
actuatorEffectiveness @3 :Float32;
|
||||
}
|
||||
|
||||
struct LateralLQRTuning {
|
||||
scale @0 :Float32;
|
||||
ki @1 :Float32;
|
||||
dcGain @2 :Float32;
|
||||
|
||||
# State space system
|
||||
a @3 :List(Float32);
|
||||
b @4 :List(Float32);
|
||||
c @5 :List(Float32);
|
||||
|
||||
k @6 :List(Float32); # LQR gain
|
||||
l @7 :List(Float32); # Kalman gain
|
||||
}
|
||||
|
||||
enum SafetyModel {
|
||||
silent @0;
|
||||
hondaNidec @1;
|
||||
toyota @2;
|
||||
elm327 @3;
|
||||
gm @4;
|
||||
hondaBoschGiraffe @5;
|
||||
ford @6;
|
||||
cadillac @7;
|
||||
hyundai @8;
|
||||
chrysler @9;
|
||||
tesla @10;
|
||||
subaru @11;
|
||||
gmPassive @12;
|
||||
mazda @13;
|
||||
nissan @14;
|
||||
volkswagen @15;
|
||||
toyotaIpas @16;
|
||||
allOutput @17;
|
||||
gmAscm @18;
|
||||
noOutput @19; # like silent but without silent CAN TXs
|
||||
hondaBoschHarness @20;
|
||||
volkswagenPq @21;
|
||||
}
|
||||
|
||||
enum SteerControlType {
|
||||
torque @0;
|
||||
angle @1;
|
||||
}
|
||||
|
||||
enum TransmissionType {
|
||||
unknown @0;
|
||||
automatic @1;
|
||||
manual @2;
|
||||
}
|
||||
|
||||
struct CarFw {
|
||||
ecu @0 :Ecu;
|
||||
fwVersion @1 :Data;
|
||||
address @2: UInt32;
|
||||
subAddress @3: UInt8;
|
||||
}
|
||||
|
||||
enum Ecu {
|
||||
eps @0;
|
||||
esp @1;
|
||||
fwdRadar @2;
|
||||
fwdCamera @3;
|
||||
engine @4;
|
||||
unknown @5;
|
||||
|
||||
# Toyota only
|
||||
dsu @6;
|
||||
apgs @7;
|
||||
}
|
||||
|
||||
enum FingerprintSource {
|
||||
can @0;
|
||||
fw @1;
|
||||
fixed @2;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -r gen/ts
|
||||
rm -r gen/js
|
||||
|
||||
mkdir gen/ts
|
||||
mkdir gen/js
|
||||
|
||||
echo "Installing needed npm modules"
|
||||
npm i capnpc-ts capnp-ts
|
||||
|
||||
capnpc -o node_modules/.bin/capnpc-ts:gen/ts log.capnp car.capnp
|
||||
capnpc -o node_modules/.bin/capnpc-ts:gen/ts car.capnp
|
||||
|
||||
cat log.capnp | egrep '\([a-zA-Z]*\.[^\s]+\.[^s]+\)' | sed 's/^.*([a-zA-Z]*\.\([a-zA-Z.]*\)).*/\1/' | while read line
|
||||
do
|
||||
TOKEN=`echo $line | sed 's/\./_/g'`
|
||||
ROOT=`echo $line | sed 's/\..*$//g'`
|
||||
cat gen/ts/log.capnp.ts | grep '^import.*'${TOKEN}
|
||||
if [[ "$?" == "1" ]]
|
||||
then
|
||||
sed -i 's/^\(import {.*\)'${ROOT}'\(,*\) \(.*\)$/\1'${ROOT}', '${TOKEN}'\2 \3/' ./gen/ts/log.capnp.ts
|
||||
fi
|
||||
done
|
||||
|
||||
tsc ./gen/ts/* --lib es2015 --outDir ./gen/js
|
||||
@@ -1,28 +0,0 @@
|
||||
# Copyright (c) 2013-2015 Sandstorm Development Group, Inc. and contributors
|
||||
# Licensed under the MIT License:
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
@0xc5f1af96651f70ea;
|
||||
|
||||
annotation package @0x9ee4c8f803b3b596 (file) : Text;
|
||||
# Name of the package, such as "org.example.foo", in which the generated code will reside.
|
||||
|
||||
annotation outerClassname @0x9b066bb4881f7cd3 (file) : Text;
|
||||
# Name of the outer class that will wrap the generated code.
|
||||
@@ -1,40 +0,0 @@
|
||||
set -e
|
||||
echo "Installing capnp"
|
||||
|
||||
cd /tmp
|
||||
VERSION=0.6.1
|
||||
wget https://capnproto.org/capnproto-c++-${VERSION}.tar.gz
|
||||
tar xvf capnproto-c++-${VERSION}.tar.gz
|
||||
cd capnproto-c++-${VERSION}
|
||||
CXXFLAGS="-fPIC" ./configure
|
||||
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
# manually build binaries statically
|
||||
g++ -std=gnu++11 -I./src -I./src -DKJ_HEADER_WARNINGS -DCAPNP_HEADER_WARNINGS -DCAPNP_INCLUDE_DIR=\"/usr/local/include\" -pthread -O2 -DNDEBUG -pthread -pthread -o .libs/capnp src/capnp/compiler/module-loader.o src/capnp/compiler/capnp.o ./.libs/libcapnpc.a ./.libs/libcapnp.a ./.libs/libkj.a -lpthread -pthread
|
||||
|
||||
g++ -std=gnu++11 -I./src -I./src -DKJ_HEADER_WARNINGS -DCAPNP_HEADER_WARNINGS -DCAPNP_INCLUDE_DIR=\"/usr/local/include\" -pthread -O2 -DNDEBUG -pthread -pthread -o .libs/capnpc-c++ src/capnp/compiler/capnpc-c++.o ./.libs/libcapnp.a ./.libs/libkj.a -lpthread -pthread
|
||||
|
||||
g++ -std=gnu++11 -I./src -I./src -DKJ_HEADER_WARNINGS -DCAPNP_HEADER_WARNINGS -DCAPNP_INCLUDE_DIR=\"/usr/local/include\" -pthread -O2 -DNDEBUG -pthread -pthread -o .libs/capnpc-capnp src/capnp/compiler/capnpc-capnp.o ./.libs/libcapnp.a ./.libs/libkj.a -lpthread -pthread
|
||||
|
||||
cp .libs/capnp /usr/local/bin/
|
||||
cp .libs/capnpc-c++ /usr/local/bin/
|
||||
cp .libs/capnpc-capnp /usr/local/bin/
|
||||
cp .libs/*.a /usr/local/lib
|
||||
|
||||
cd /tmp
|
||||
echo "Installing c-capnp"
|
||||
git clone https://github.com/commaai/c-capnproto.git
|
||||
cd c-capnproto
|
||||
git submodule update --init --recursive
|
||||
autoreconf -f -i -s
|
||||
CXXFLAGS="-fPIC" ./configure
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
# manually build binaries statically
|
||||
gcc -fPIC -o .libs/capnpc-c compiler/capnpc-c.o compiler/schema.capnp.o compiler/str.o ./.libs/libcapnp_c.a
|
||||
|
||||
cp .libs/capnpc-c /usr/local/bin/
|
||||
cp .libs/*.a /usr/local/lib
|
||||
-1957
File diff suppressed because it is too large
Load Diff
@@ -1,53 +0,0 @@
|
||||
using Cxx = import "./include/c++.capnp";
|
||||
$Cxx.namespace("cereal");
|
||||
|
||||
using Java = import "./include/java.capnp";
|
||||
$Java.package("ai.comma.openpilot.cereal");
|
||||
$Java.outerClassname("Map");
|
||||
|
||||
@0xa086df597ef5d7a0;
|
||||
|
||||
# Geometry
|
||||
struct Point {
|
||||
x @0: Float64;
|
||||
y @1: Float64;
|
||||
z @2: Float64;
|
||||
}
|
||||
|
||||
struct PolyLine {
|
||||
points @0: List(Point);
|
||||
}
|
||||
|
||||
# Map features
|
||||
struct Lane {
|
||||
id @0 :Text;
|
||||
|
||||
leftBoundary @1 :LaneBoundary;
|
||||
rightBoundary @2 :LaneBoundary;
|
||||
|
||||
leftAdjacentId @3 :Text;
|
||||
rightAdjacentId @4 :Text;
|
||||
|
||||
inboundIds @5 :List(Text);
|
||||
outboundIds @6 :List(Text);
|
||||
|
||||
struct LaneBoundary {
|
||||
polyLine @0 :PolyLine;
|
||||
startHeading @1 :Float32; # WRT north
|
||||
}
|
||||
}
|
||||
|
||||
# Map tiles
|
||||
struct TileSummary {
|
||||
version @0 :Text;
|
||||
updatedAt @1 :UInt64; # Millis since epoch
|
||||
|
||||
level @2 :UInt8;
|
||||
x @3 :UInt16;
|
||||
y @4 :UInt16;
|
||||
}
|
||||
|
||||
struct MapTile {
|
||||
summary @0 :TileSummary;
|
||||
lanes @1 :List(Lane);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
demo
|
||||
bridge
|
||||
test_runner
|
||||
*.o
|
||||
*.os
|
||||
*.d
|
||||
*.a
|
||||
*.so
|
||||
messaging_pyx.cpp
|
||||
build/
|
||||
@@ -1,221 +0,0 @@
|
||||
# must be build with scons
|
||||
from .messaging_pyx import Context, Poller, SubSocket, PubSocket # pylint: disable=no-name-in-module, import-error
|
||||
from .messaging_pyx import MultiplePublishersError, MessagingError # pylint: disable=no-name-in-module, import-error
|
||||
import capnp
|
||||
|
||||
assert MultiplePublishersError
|
||||
assert MessagingError
|
||||
|
||||
from cereal import log
|
||||
from cereal.services import service_list
|
||||
|
||||
# sec_since_boot is faster, but allow to run standalone too
|
||||
try:
|
||||
from common.realtime import sec_since_boot
|
||||
except ImportError:
|
||||
import time
|
||||
sec_since_boot = time.time
|
||||
print("Warning, using python time.time() instead of faster sec_since_boot")
|
||||
|
||||
context = Context()
|
||||
|
||||
def new_message():
|
||||
dat = log.Event.new_message()
|
||||
dat.logMonoTime = int(sec_since_boot() * 1e9)
|
||||
dat.valid = True
|
||||
return dat
|
||||
|
||||
def pub_sock(endpoint):
|
||||
sock = PubSocket()
|
||||
sock.connect(context, endpoint)
|
||||
return sock
|
||||
|
||||
def sub_sock(endpoint, poller=None, addr="127.0.0.1", conflate=False, timeout=None):
|
||||
sock = SubSocket()
|
||||
addr = addr.encode('utf8')
|
||||
sock.connect(context, endpoint, addr, conflate)
|
||||
|
||||
if timeout is not None:
|
||||
sock.setTimeout(timeout)
|
||||
|
||||
if poller is not None:
|
||||
poller.registerSocket(sock)
|
||||
return sock
|
||||
|
||||
|
||||
def drain_sock_raw(sock, wait_for_one=False):
|
||||
"""Receive all message currently available on the queue"""
|
||||
ret = []
|
||||
while 1:
|
||||
if wait_for_one and len(ret) == 0:
|
||||
dat = sock.receive()
|
||||
else:
|
||||
dat = sock.receive(non_blocking=True)
|
||||
|
||||
if dat is None:
|
||||
break
|
||||
|
||||
ret.append(dat)
|
||||
|
||||
return ret
|
||||
|
||||
def drain_sock(sock, wait_for_one=False):
|
||||
"""Receive all message currently available on the queue"""
|
||||
ret = []
|
||||
while 1:
|
||||
if wait_for_one and len(ret) == 0:
|
||||
dat = sock.receive()
|
||||
else:
|
||||
dat = sock.receive(non_blocking=True)
|
||||
|
||||
if dat is None: # Timeout hit
|
||||
break
|
||||
|
||||
dat = log.Event.from_bytes(dat)
|
||||
ret.append(dat)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
# TODO: print when we drop packets?
|
||||
def recv_sock(sock, wait=False):
|
||||
"""Same as drain sock, but only returns latest message. Consider using conflate instead."""
|
||||
dat = None
|
||||
|
||||
while 1:
|
||||
if wait and dat is None:
|
||||
rcv = sock.receive()
|
||||
else:
|
||||
rcv = sock.receive(non_blocking=True)
|
||||
|
||||
if rcv is None: # Timeout hit
|
||||
break
|
||||
|
||||
dat = rcv
|
||||
|
||||
if dat is not None:
|
||||
dat = log.Event.from_bytes(dat)
|
||||
|
||||
return dat
|
||||
|
||||
def recv_one(sock):
|
||||
dat = sock.receive()
|
||||
if dat is not None:
|
||||
dat = log.Event.from_bytes(dat)
|
||||
return dat
|
||||
|
||||
def recv_one_or_none(sock):
|
||||
dat = sock.receive(non_blocking=True)
|
||||
if dat is not None:
|
||||
dat = log.Event.from_bytes(dat)
|
||||
return dat
|
||||
|
||||
def recv_one_retry(sock):
|
||||
"""Keep receiving until we get a message"""
|
||||
while True:
|
||||
dat = sock.receive()
|
||||
if dat is not None:
|
||||
return log.Event.from_bytes(dat)
|
||||
|
||||
# TODO: This does not belong in messaging
|
||||
def get_one_can(logcan):
|
||||
while True:
|
||||
can = recv_one_retry(logcan)
|
||||
if len(can.can) > 0:
|
||||
return can
|
||||
|
||||
class SubMaster():
|
||||
def __init__(self, services, ignore_alive=None, addr="127.0.0.1"):
|
||||
self.poller = Poller()
|
||||
self.frame = -1
|
||||
self.updated = {s : False for s in services}
|
||||
self.rcv_time = {s : 0. for s in services}
|
||||
self.rcv_frame = {s : 0 for s in services}
|
||||
self.alive = {s : False for s in services}
|
||||
self.sock = {}
|
||||
self.freq = {}
|
||||
self.data = {}
|
||||
self.logMonoTime = {}
|
||||
self.valid = {}
|
||||
|
||||
if ignore_alive is not None:
|
||||
self.ignore_alive = ignore_alive
|
||||
else:
|
||||
self.ignore_alive = []
|
||||
|
||||
for s in services:
|
||||
if addr is not None:
|
||||
self.sock[s] = sub_sock(s, poller=self.poller, addr=addr, conflate=True)
|
||||
self.freq[s] = service_list[s].frequency
|
||||
|
||||
data = new_message()
|
||||
try:
|
||||
data.init(s)
|
||||
except capnp.lib.capnp.KjException:
|
||||
# lists
|
||||
data.init(s, 0)
|
||||
|
||||
self.data[s] = getattr(data, s)
|
||||
self.logMonoTime[s] = 0
|
||||
self.valid[s] = data.valid
|
||||
|
||||
def __getitem__(self, s):
|
||||
return self.data[s]
|
||||
|
||||
def update(self, timeout=1000):
|
||||
msgs = []
|
||||
for sock in self.poller.poll(timeout):
|
||||
msgs.append(recv_one_or_none(sock))
|
||||
self.update_msgs(sec_since_boot(), msgs)
|
||||
|
||||
def update_msgs(self, cur_time, msgs):
|
||||
# TODO: add optional input that specify the service to wait for
|
||||
self.frame += 1
|
||||
self.updated = dict.fromkeys(self.updated, False)
|
||||
for msg in msgs:
|
||||
if msg is None:
|
||||
continue
|
||||
|
||||
s = msg.which()
|
||||
self.updated[s] = True
|
||||
self.rcv_time[s] = cur_time
|
||||
self.rcv_frame[s] = self.frame
|
||||
self.data[s] = getattr(msg, s)
|
||||
self.logMonoTime[s] = msg.logMonoTime
|
||||
self.valid[s] = msg.valid
|
||||
|
||||
for s in self.data:
|
||||
# arbitrary small number to avoid float comparison. If freq is 0, we can skip the check
|
||||
if self.freq[s] > 1e-5:
|
||||
# alive if delay is within 10x the expected frequency
|
||||
self.alive[s] = (cur_time - self.rcv_time[s]) < (10. / self.freq[s])
|
||||
else:
|
||||
self.alive[s] = True
|
||||
|
||||
def all_alive(self, service_list=None):
|
||||
if service_list is None: # check all
|
||||
service_list = self.alive.keys()
|
||||
return all(self.alive[s] for s in service_list if s not in self.ignore_alive)
|
||||
|
||||
def all_valid(self, service_list=None):
|
||||
if service_list is None: # check all
|
||||
service_list = self.valid.keys()
|
||||
return all(self.valid[s] for s in service_list)
|
||||
|
||||
def all_alive_and_valid(self, service_list=None):
|
||||
if service_list is None: # check all
|
||||
service_list = self.alive.keys()
|
||||
return self.all_alive(service_list=service_list) and self.all_valid(service_list=service_list)
|
||||
|
||||
|
||||
class PubMaster():
|
||||
def __init__(self, services):
|
||||
self.sock = {}
|
||||
for s in services:
|
||||
self.sock[s] = pub_sock(s)
|
||||
|
||||
def send(self, s, dat):
|
||||
# accept either bytes or capnp builder
|
||||
if not isinstance(dat, bytes):
|
||||
dat = dat.to_bytes()
|
||||
self.sock[s].send(dat)
|
||||
@@ -1,64 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include <csignal>
|
||||
#include <map>
|
||||
|
||||
typedef void (*sighandler_t)(int sig);
|
||||
|
||||
#include "services.h"
|
||||
|
||||
#include "impl_msgq.hpp"
|
||||
#include "impl_zmq.hpp"
|
||||
|
||||
void sigpipe_handler(int sig) {
|
||||
assert(sig == SIGPIPE);
|
||||
std::cout << "SIGPIPE received" << std::endl;
|
||||
}
|
||||
|
||||
static std::vector<std::string> get_services() {
|
||||
std::vector<std::string> name_list;
|
||||
|
||||
for (const auto& it : services) {
|
||||
std::string name = it.name;
|
||||
if (name == "plusFrame" || name == "uiLayoutState") continue;
|
||||
name_list.push_back(name);
|
||||
}
|
||||
|
||||
return name_list;
|
||||
}
|
||||
|
||||
|
||||
int main(void){
|
||||
signal(SIGPIPE, (sighandler_t)sigpipe_handler);
|
||||
|
||||
auto endpoints = get_services();
|
||||
|
||||
std::map<SubSocket*, PubSocket*> sub2pub;
|
||||
|
||||
Context *zmq_context = new ZMQContext();
|
||||
Context *msgq_context = new MSGQContext();
|
||||
Poller *poller = new MSGQPoller();
|
||||
|
||||
for (auto endpoint: endpoints){
|
||||
SubSocket * msgq_sock = new MSGQSubSocket();
|
||||
msgq_sock->connect(msgq_context, endpoint, "127.0.0.1", false);
|
||||
poller->registerSocket(msgq_sock);
|
||||
|
||||
PubSocket * zmq_sock = new ZMQPubSocket();
|
||||
zmq_sock->connect(zmq_context, endpoint);
|
||||
|
||||
sub2pub[msgq_sock] = zmq_sock;
|
||||
}
|
||||
|
||||
|
||||
while (true){
|
||||
for (auto sub_sock : poller->poll(100)){
|
||||
Message * msg = sub_sock->receive();
|
||||
if (msg == NULL) continue;
|
||||
sub2pub[sub_sock]->sendMessage(msg);
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,50 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <cstddef>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <cassert>
|
||||
|
||||
#include "messaging.hpp"
|
||||
#include "impl_zmq.hpp"
|
||||
|
||||
#define MSGS 1e5
|
||||
|
||||
int main() {
|
||||
Context * c = Context::create();
|
||||
SubSocket * sub_sock = SubSocket::create(c, "controlsState");
|
||||
PubSocket * pub_sock = PubSocket::create(c, "controlsState");
|
||||
|
||||
char data[8];
|
||||
|
||||
Poller * poller = Poller::create({sub_sock});
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
for (uint64_t i = 0; i < MSGS; i++){
|
||||
*(uint64_t*)data = i;
|
||||
pub_sock->send(data, 8);
|
||||
|
||||
auto r = poller->poll(100);
|
||||
|
||||
for (auto p : r){
|
||||
Message * m = p->receive();
|
||||
uint64_t ii = *(uint64_t*)m->getData();
|
||||
assert(i == ii);
|
||||
delete m;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
double elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() / 1e9;
|
||||
double throughput = ((double) MSGS / (double) elapsed);
|
||||
std::cout << throughput << " msg/s" << std::endl;
|
||||
|
||||
delete poller;
|
||||
delete sub_sock;
|
||||
delete pub_sock;
|
||||
delete c;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import time
|
||||
|
||||
from messaging_pyx import Context, Poller, SubSocket, PubSocket # pylint: disable=no-name-in-module, import-error
|
||||
|
||||
MSGS = 1e5
|
||||
|
||||
if __name__ == "__main__":
|
||||
c = Context()
|
||||
sub_sock = SubSocket()
|
||||
pub_sock = PubSocket()
|
||||
|
||||
sub_sock.connect(c, "controlsState")
|
||||
pub_sock.connect(c, "controlsState")
|
||||
|
||||
|
||||
poller = Poller()
|
||||
poller.registerSocket(sub_sock)
|
||||
|
||||
t = time.time()
|
||||
for i in range(int(MSGS)):
|
||||
bts = i.to_bytes(4, 'little')
|
||||
pub_sock.send(bts)
|
||||
|
||||
for s in poller.poll(100):
|
||||
dat = s.receive()
|
||||
ii = int.from_bytes(dat, 'little')
|
||||
assert(i == ii)
|
||||
|
||||
dt = time.time() - t
|
||||
print("%.1f msg/s" % (MSGS / dt))
|
||||
@@ -1,195 +0,0 @@
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
|
||||
|
||||
#include "impl_msgq.hpp"
|
||||
|
||||
volatile sig_atomic_t msgq_do_exit = 0;
|
||||
|
||||
void sig_handler(int signal) {
|
||||
assert(signal == SIGINT || signal == SIGTERM);
|
||||
msgq_do_exit = 1;
|
||||
}
|
||||
|
||||
|
||||
MSGQContext::MSGQContext() {
|
||||
}
|
||||
|
||||
MSGQContext::~MSGQContext() {
|
||||
}
|
||||
|
||||
void MSGQMessage::init(size_t sz) {
|
||||
size = sz;
|
||||
data = new char[size];
|
||||
}
|
||||
|
||||
void MSGQMessage::init(char * d, size_t sz) {
|
||||
size = sz;
|
||||
data = new char[size];
|
||||
memcpy(data, d, size);
|
||||
}
|
||||
|
||||
void MSGQMessage::takeOwnership(char * d, size_t sz) {
|
||||
size = sz;
|
||||
data = d;
|
||||
}
|
||||
|
||||
void MSGQMessage::close() {
|
||||
if (size > 0){
|
||||
delete[] data;
|
||||
}
|
||||
size = 0;
|
||||
}
|
||||
|
||||
MSGQMessage::~MSGQMessage() {
|
||||
this->close();
|
||||
}
|
||||
|
||||
|
||||
int MSGQSubSocket::connect(Context *context, std::string endpoint, std::string address, bool conflate){
|
||||
assert(context);
|
||||
assert(address == "127.0.0.1");
|
||||
|
||||
q = new msgq_queue_t;
|
||||
int r = msgq_new_queue(q, endpoint.c_str(), DEFAULT_SEGMENT_SIZE);
|
||||
if (r != 0){
|
||||
return r;
|
||||
}
|
||||
|
||||
msgq_init_subscriber(q);
|
||||
|
||||
if (conflate){
|
||||
q->read_conflate = true;
|
||||
}
|
||||
|
||||
timeout = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Message * MSGQSubSocket::receive(bool non_blocking){
|
||||
msgq_do_exit = 0;
|
||||
|
||||
void (*prev_handler_sigint)(int);
|
||||
void (*prev_handler_sigterm)(int);
|
||||
if (!non_blocking){
|
||||
prev_handler_sigint = std::signal(SIGINT, sig_handler);
|
||||
prev_handler_sigterm = std::signal(SIGTERM, sig_handler);
|
||||
}
|
||||
|
||||
msgq_msg_t msg;
|
||||
|
||||
MSGQMessage *r = NULL;
|
||||
|
||||
int rc = msgq_msg_recv(&msg, q);
|
||||
|
||||
// Hack to implement blocking read with a poller. Don't use this
|
||||
while (!non_blocking && rc == 0 && msgq_do_exit == 0){
|
||||
msgq_pollitem_t items[1];
|
||||
items[0].q = q;
|
||||
|
||||
int t = (timeout != -1) ? timeout : 100;
|
||||
|
||||
int n = msgq_poll(items, 1, t);
|
||||
rc = msgq_msg_recv(&msg, q);
|
||||
|
||||
// The poll indicated a message was ready, but the receive failed. Try again
|
||||
if (n == 1 && rc == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
if (timeout != -1){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!non_blocking){
|
||||
std::signal(SIGINT, prev_handler_sigint);
|
||||
std::signal(SIGTERM, prev_handler_sigterm);
|
||||
}
|
||||
|
||||
errno = msgq_do_exit ? EINTR : 0;
|
||||
|
||||
if (rc > 0){
|
||||
if (msgq_do_exit){
|
||||
msgq_msg_close(&msg); // Free unused message on exit
|
||||
} else {
|
||||
r = new MSGQMessage;
|
||||
r->takeOwnership(msg.data, msg.size);
|
||||
}
|
||||
}
|
||||
|
||||
return (Message*)r;
|
||||
}
|
||||
|
||||
void MSGQSubSocket::setTimeout(int t){
|
||||
timeout = t;
|
||||
}
|
||||
|
||||
MSGQSubSocket::~MSGQSubSocket(){
|
||||
if (q != NULL){
|
||||
msgq_close_queue(q);
|
||||
delete q;
|
||||
}
|
||||
}
|
||||
|
||||
int MSGQPubSocket::connect(Context *context, std::string endpoint){
|
||||
assert(context);
|
||||
|
||||
q = new msgq_queue_t;
|
||||
msgq_new_queue(q, endpoint.c_str(), DEFAULT_SEGMENT_SIZE);
|
||||
msgq_init_publisher(q);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MSGQPubSocket::sendMessage(Message *message){
|
||||
msgq_msg_t msg;
|
||||
msg.data = message->getData();
|
||||
msg.size = message->getSize();
|
||||
|
||||
return msgq_msg_send(&msg, q);
|
||||
}
|
||||
|
||||
int MSGQPubSocket::send(char *data, size_t size){
|
||||
msgq_msg_t msg;
|
||||
msg.data = data;
|
||||
msg.size = size;
|
||||
|
||||
return msgq_msg_send(&msg, q);
|
||||
}
|
||||
|
||||
MSGQPubSocket::~MSGQPubSocket(){
|
||||
if (q != NULL){
|
||||
msgq_close_queue(q);
|
||||
delete q;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MSGQPoller::registerSocket(SubSocket * socket){
|
||||
assert(num_polls + 1 < MAX_POLLERS);
|
||||
polls[num_polls].q = (msgq_queue_t*)socket->getRawSocket();
|
||||
|
||||
sockets.push_back(socket);
|
||||
num_polls++;
|
||||
}
|
||||
|
||||
std::vector<SubSocket*> MSGQPoller::poll(int timeout){
|
||||
std::vector<SubSocket*> r;
|
||||
|
||||
msgq_poll(polls, num_polls, timeout);
|
||||
for (size_t i = 0; i < num_polls; i++){
|
||||
if (polls[i].revents){
|
||||
r.push_back(sockets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
#pragma once
|
||||
#include "messaging.hpp"
|
||||
#include "msgq.hpp"
|
||||
#include <zmq.h>
|
||||
#include <string>
|
||||
|
||||
#define MAX_POLLERS 128
|
||||
|
||||
class MSGQContext : public Context {
|
||||
private:
|
||||
void * context = NULL;
|
||||
public:
|
||||
MSGQContext();
|
||||
void * getRawContext() {return context;}
|
||||
~MSGQContext();
|
||||
};
|
||||
|
||||
class MSGQMessage : public Message {
|
||||
private:
|
||||
char * data;
|
||||
size_t size;
|
||||
public:
|
||||
void init(size_t size);
|
||||
void init(char *data, size_t size);
|
||||
void takeOwnership(char *data, size_t size);
|
||||
size_t getSize(){return size;}
|
||||
char * getData(){return data;}
|
||||
void close();
|
||||
~MSGQMessage();
|
||||
};
|
||||
|
||||
class MSGQSubSocket : public SubSocket {
|
||||
private:
|
||||
msgq_queue_t * q = NULL;
|
||||
int timeout;
|
||||
public:
|
||||
int connect(Context *context, std::string endpoint, std::string address, bool conflate=false);
|
||||
void setTimeout(int timeout);
|
||||
void * getRawSocket() {return (void*)q;}
|
||||
Message *receive(bool non_blocking=false);
|
||||
~MSGQSubSocket();
|
||||
};
|
||||
|
||||
class MSGQPubSocket : public PubSocket {
|
||||
private:
|
||||
msgq_queue_t * q = NULL;
|
||||
public:
|
||||
int connect(Context *context, std::string endpoint);
|
||||
int sendMessage(Message *message);
|
||||
int send(char *data, size_t size);
|
||||
~MSGQPubSocket();
|
||||
};
|
||||
|
||||
class MSGQPoller : public Poller {
|
||||
private:
|
||||
std::vector<SubSocket*> sockets;
|
||||
msgq_pollitem_t polls[MAX_POLLERS];
|
||||
size_t num_polls = 0;
|
||||
|
||||
public:
|
||||
void registerSocket(SubSocket *socket);
|
||||
std::vector<SubSocket*> poll(int timeout);
|
||||
~MSGQPoller(){};
|
||||
};
|
||||
@@ -1,155 +0,0 @@
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <cerrno>
|
||||
|
||||
#include <zmq.h>
|
||||
|
||||
#include "services.h"
|
||||
#include "impl_zmq.hpp"
|
||||
|
||||
static int get_port(std::string endpoint) {
|
||||
int port = -1;
|
||||
for (const auto& it : services) {
|
||||
std::string name = it.name;
|
||||
if (name == endpoint) {
|
||||
port = it.port;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(port >= 0);
|
||||
return port;
|
||||
}
|
||||
|
||||
ZMQContext::ZMQContext() {
|
||||
context = zmq_ctx_new();
|
||||
}
|
||||
|
||||
ZMQContext::~ZMQContext() {
|
||||
zmq_ctx_term(context);
|
||||
}
|
||||
|
||||
void ZMQMessage::init(size_t sz) {
|
||||
size = sz;
|
||||
data = new char[size];
|
||||
}
|
||||
|
||||
void ZMQMessage::init(char * d, size_t sz) {
|
||||
size = sz;
|
||||
data = new char[size];
|
||||
memcpy(data, d, size);
|
||||
}
|
||||
|
||||
void ZMQMessage::close() {
|
||||
if (size > 0){
|
||||
delete[] data;
|
||||
}
|
||||
size = 0;
|
||||
}
|
||||
|
||||
ZMQMessage::~ZMQMessage() {
|
||||
this->close();
|
||||
}
|
||||
|
||||
|
||||
int ZMQSubSocket::connect(Context *context, std::string endpoint, std::string address, bool conflate){
|
||||
sock = zmq_socket(context->getRawContext(), ZMQ_SUB);
|
||||
if (sock == NULL){
|
||||
return -1;
|
||||
}
|
||||
|
||||
zmq_setsockopt(sock, ZMQ_SUBSCRIBE, "", 0);
|
||||
|
||||
if (conflate){
|
||||
int arg = 1;
|
||||
zmq_setsockopt(sock, ZMQ_CONFLATE, &arg, sizeof(int));
|
||||
}
|
||||
|
||||
int reconnect_ivl = 500;
|
||||
zmq_setsockopt(sock, ZMQ_RECONNECT_IVL_MAX, &reconnect_ivl, sizeof(reconnect_ivl));
|
||||
|
||||
full_endpoint = "tcp://" + address + ":";
|
||||
full_endpoint += std::to_string(get_port(endpoint));
|
||||
|
||||
return zmq_connect(sock, full_endpoint.c_str());
|
||||
}
|
||||
|
||||
|
||||
Message * ZMQSubSocket::receive(bool non_blocking){
|
||||
zmq_msg_t msg;
|
||||
assert(zmq_msg_init(&msg) == 0);
|
||||
|
||||
int flags = non_blocking ? ZMQ_DONTWAIT : 0;
|
||||
int rc = zmq_msg_recv(&msg, sock, flags);
|
||||
Message *r = NULL;
|
||||
|
||||
if (rc >= 0){
|
||||
// Make a copy to ensure the data is aligned
|
||||
r = new ZMQMessage;
|
||||
r->init((char*)zmq_msg_data(&msg), zmq_msg_size(&msg));
|
||||
}
|
||||
|
||||
zmq_msg_close(&msg);
|
||||
return r;
|
||||
}
|
||||
|
||||
void ZMQSubSocket::setTimeout(int timeout){
|
||||
zmq_setsockopt(sock, ZMQ_RCVTIMEO, &timeout, sizeof(int));
|
||||
}
|
||||
|
||||
ZMQSubSocket::~ZMQSubSocket(){
|
||||
zmq_close(sock);
|
||||
}
|
||||
|
||||
int ZMQPubSocket::connect(Context *context, std::string endpoint){
|
||||
sock = zmq_socket(context->getRawContext(), ZMQ_PUB);
|
||||
if (sock == NULL){
|
||||
return -1;
|
||||
}
|
||||
|
||||
full_endpoint = "tcp://*:";
|
||||
full_endpoint += std::to_string(get_port(endpoint));
|
||||
|
||||
return zmq_bind(sock, full_endpoint.c_str());
|
||||
}
|
||||
|
||||
int ZMQPubSocket::sendMessage(Message *message){
|
||||
return zmq_send(sock, message->getData(), message->getSize(), ZMQ_DONTWAIT);
|
||||
}
|
||||
|
||||
int ZMQPubSocket::send(char *data, size_t size){
|
||||
return zmq_send(sock, data, size, ZMQ_DONTWAIT);
|
||||
}
|
||||
|
||||
ZMQPubSocket::~ZMQPubSocket(){
|
||||
zmq_close(sock);
|
||||
}
|
||||
|
||||
|
||||
void ZMQPoller::registerSocket(SubSocket * socket){
|
||||
assert(num_polls + 1 < MAX_POLLERS);
|
||||
polls[num_polls].socket = socket->getRawSocket();
|
||||
polls[num_polls].events = ZMQ_POLLIN;
|
||||
|
||||
sockets.push_back(socket);
|
||||
num_polls++;
|
||||
}
|
||||
|
||||
std::vector<SubSocket*> ZMQPoller::poll(int timeout){
|
||||
std::vector<SubSocket*> r;
|
||||
|
||||
int rc = zmq_poll(polls, num_polls, timeout);
|
||||
if (rc < 0){
|
||||
return r;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_polls; i++){
|
||||
if (polls[i].revents){
|
||||
r.push_back(sockets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
#include "messaging.hpp"
|
||||
#include <zmq.h>
|
||||
#include <string>
|
||||
|
||||
#define MAX_POLLERS 128
|
||||
|
||||
class ZMQContext : public Context {
|
||||
private:
|
||||
void * context = NULL;
|
||||
public:
|
||||
ZMQContext();
|
||||
void * getRawContext() {return context;}
|
||||
~ZMQContext();
|
||||
};
|
||||
|
||||
class ZMQMessage : public Message {
|
||||
private:
|
||||
char * data;
|
||||
size_t size;
|
||||
public:
|
||||
void init(size_t size);
|
||||
void init(char *data, size_t size);
|
||||
size_t getSize(){return size;}
|
||||
char * getData(){return data;}
|
||||
void close();
|
||||
~ZMQMessage();
|
||||
};
|
||||
|
||||
class ZMQSubSocket : public SubSocket {
|
||||
private:
|
||||
void * sock;
|
||||
std::string full_endpoint;
|
||||
public:
|
||||
int connect(Context *context, std::string endpoint, std::string address, bool conflate=false);
|
||||
void setTimeout(int timeout);
|
||||
void * getRawSocket() {return sock;}
|
||||
Message *receive(bool non_blocking=false);
|
||||
~ZMQSubSocket();
|
||||
};
|
||||
|
||||
class ZMQPubSocket : public PubSocket {
|
||||
private:
|
||||
void * sock;
|
||||
std::string full_endpoint;
|
||||
public:
|
||||
int connect(Context *context, std::string endpoint);
|
||||
int sendMessage(Message *message);
|
||||
int send(char *data, size_t size);
|
||||
~ZMQPubSocket();
|
||||
};
|
||||
|
||||
class ZMQPoller : public Poller {
|
||||
private:
|
||||
std::vector<SubSocket*> sockets;
|
||||
zmq_pollitem_t polls[MAX_POLLERS];
|
||||
size_t num_polls = 0;
|
||||
|
||||
public:
|
||||
void registerSocket(SubSocket *socket);
|
||||
std::vector<SubSocket*> poll(int timeout);
|
||||
~ZMQPoller(){};
|
||||
};
|
||||
@@ -1,117 +0,0 @@
|
||||
#include "messaging.hpp"
|
||||
#include "impl_zmq.hpp"
|
||||
#include "impl_msgq.hpp"
|
||||
|
||||
Context * Context::create(){
|
||||
Context * c;
|
||||
if (std::getenv("ZMQ")){
|
||||
c = new ZMQContext();
|
||||
} else {
|
||||
c = new MSGQContext();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
SubSocket * SubSocket::create(){
|
||||
SubSocket * s;
|
||||
if (std::getenv("ZMQ")){
|
||||
s = new ZMQSubSocket();
|
||||
} else {
|
||||
s = new MSGQSubSocket();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
SubSocket * SubSocket::create(Context * context, std::string endpoint){
|
||||
SubSocket *s = SubSocket::create();
|
||||
int r = s->connect(context, endpoint, "127.0.0.1");
|
||||
|
||||
if (r == 0) {
|
||||
return s;
|
||||
} else {
|
||||
delete s;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
SubSocket * SubSocket::create(Context * context, std::string endpoint, std::string address){
|
||||
SubSocket *s = SubSocket::create();
|
||||
int r = s->connect(context, endpoint, address);
|
||||
|
||||
if (r == 0) {
|
||||
return s;
|
||||
} else {
|
||||
delete s;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
SubSocket * SubSocket::create(Context * context, std::string endpoint, std::string address, bool conflate){
|
||||
SubSocket *s = SubSocket::create();
|
||||
int r = s->connect(context, endpoint, address, conflate);
|
||||
|
||||
if (r == 0) {
|
||||
return s;
|
||||
} else {
|
||||
delete s;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
PubSocket * PubSocket::create(){
|
||||
PubSocket * s;
|
||||
if (std::getenv("ZMQ")){
|
||||
s = new ZMQPubSocket();
|
||||
} else {
|
||||
s = new MSGQPubSocket();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
PubSocket * PubSocket::create(Context * context, std::string endpoint){
|
||||
PubSocket *s = PubSocket::create();
|
||||
int r = s->connect(context, endpoint);
|
||||
|
||||
if (r == 0) {
|
||||
return s;
|
||||
} else {
|
||||
delete s;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Poller * Poller::create(){
|
||||
Poller * p;
|
||||
if (std::getenv("ZMQ")){
|
||||
p = new ZMQPoller();
|
||||
} else {
|
||||
p = new MSGQPoller();
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
Poller * Poller::create(std::vector<SubSocket*> sockets){
|
||||
Poller * p = Poller::create();
|
||||
|
||||
for (auto s : sockets){
|
||||
p->registerSocket(s);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
extern "C" Context * messaging_context_create() {
|
||||
return Context::create();
|
||||
}
|
||||
|
||||
extern "C" SubSocket * messaging_subsocket_create(Context* context, const char* endpoint) {
|
||||
return SubSocket::create(context, std::string(endpoint));
|
||||
}
|
||||
|
||||
extern "C" PubSocket * messaging_pubsocket_create(Context* context, const char* endpoint) {
|
||||
return PubSocket::create(context, std::string(endpoint));
|
||||
}
|
||||
|
||||
extern "C" Poller * messaging_poller_create(SubSocket** sockets, int size) {
|
||||
std::vector<SubSocket*> socketsVec(sockets, sockets + size);
|
||||
return Poller::create(socketsVec);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#define MSG_MULTIPLE_PUBLISHERS 100
|
||||
|
||||
class Context {
|
||||
public:
|
||||
virtual void * getRawContext() = 0;
|
||||
static Context * create();
|
||||
virtual ~Context(){};
|
||||
};
|
||||
|
||||
class Message {
|
||||
public:
|
||||
virtual void init(size_t size) = 0;
|
||||
virtual void init(char * data, size_t size) = 0;
|
||||
virtual void close() = 0;
|
||||
virtual size_t getSize() = 0;
|
||||
virtual char * getData() = 0;
|
||||
virtual ~Message(){};
|
||||
};
|
||||
|
||||
|
||||
class SubSocket {
|
||||
public:
|
||||
virtual int connect(Context *context, std::string endpoint, std::string address, bool conflate=false) = 0;
|
||||
virtual void setTimeout(int timeout) = 0;
|
||||
virtual Message *receive(bool non_blocking=false) = 0;
|
||||
virtual void * getRawSocket() = 0;
|
||||
static SubSocket * create();
|
||||
static SubSocket * create(Context * context, std::string endpoint);
|
||||
static SubSocket * create(Context * context, std::string endpoint, std::string address);
|
||||
static SubSocket * create(Context * context, std::string endpoint, std::string address, bool conflate);
|
||||
virtual ~SubSocket(){};
|
||||
};
|
||||
|
||||
class PubSocket {
|
||||
public:
|
||||
virtual int connect(Context *context, std::string endpoint) = 0;
|
||||
virtual int sendMessage(Message *message) = 0;
|
||||
virtual int send(char *data, size_t size) = 0;
|
||||
static PubSocket * create();
|
||||
static PubSocket * create(Context * context, std::string endpoint);
|
||||
virtual ~PubSocket(){};
|
||||
};
|
||||
|
||||
class Poller {
|
||||
public:
|
||||
virtual void registerSocket(SubSocket *socket) = 0;
|
||||
virtual std::vector<SubSocket*> poll(int timeout) = 0;
|
||||
static Poller * create();
|
||||
static Poller * create(std::vector<SubSocket*> sockets);
|
||||
virtual ~Poller(){};
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
# distutils: language = c++
|
||||
#cython: language_level=3
|
||||
|
||||
from libcpp.string cimport string
|
||||
from libcpp.vector cimport vector
|
||||
from libcpp cimport bool
|
||||
|
||||
|
||||
cdef extern from "messaging.hpp":
|
||||
cdef cppclass Context:
|
||||
@staticmethod
|
||||
Context * create()
|
||||
|
||||
cdef cppclass Message:
|
||||
void init(size_t)
|
||||
void init(char *, size_t)
|
||||
void close()
|
||||
size_t getSize()
|
||||
char *getData()
|
||||
|
||||
cdef cppclass SubSocket:
|
||||
@staticmethod
|
||||
SubSocket * create()
|
||||
int connect(Context *, string, string, bool)
|
||||
Message * receive(bool)
|
||||
void setTimeout(int)
|
||||
|
||||
cdef cppclass PubSocket:
|
||||
@staticmethod
|
||||
PubSocket * create()
|
||||
int connect(Context *, string)
|
||||
int sendMessage(Message *)
|
||||
int send(char *, size_t)
|
||||
|
||||
cdef cppclass Poller:
|
||||
@staticmethod
|
||||
Poller * create()
|
||||
void registerSocket(SubSocket *)
|
||||
vector[SubSocket*] poll(int) nogil
|
||||
@@ -1,151 +0,0 @@
|
||||
# distutils: language = c++
|
||||
# cython: c_string_encoding=ascii, language_level=3
|
||||
|
||||
import sys
|
||||
from libcpp.string cimport string
|
||||
from libcpp cimport bool
|
||||
from libc cimport errno
|
||||
|
||||
|
||||
from messaging cimport Context as cppContext
|
||||
from messaging cimport SubSocket as cppSubSocket
|
||||
from messaging cimport PubSocket as cppPubSocket
|
||||
from messaging cimport Poller as cppPoller
|
||||
from messaging cimport Message as cppMessage
|
||||
|
||||
|
||||
class MessagingError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MultiplePublishersError(MessagingError):
|
||||
pass
|
||||
|
||||
|
||||
cdef class Context:
|
||||
cdef cppContext * context
|
||||
|
||||
def __cinit__(self):
|
||||
self.context = cppContext.create()
|
||||
|
||||
def term(self):
|
||||
del self.context
|
||||
self.context = NULL
|
||||
|
||||
def __dealloc__(self):
|
||||
pass
|
||||
# Deleting the context will hang if sockets are still active
|
||||
# TODO: Figure out a way to make sure the context is closed last
|
||||
# del self.context
|
||||
|
||||
|
||||
cdef class Poller:
|
||||
cdef cppPoller * poller
|
||||
cdef list sub_sockets
|
||||
|
||||
def __cinit__(self):
|
||||
self.sub_sockets = []
|
||||
self.poller = cppPoller.create()
|
||||
|
||||
def __dealloc__(self):
|
||||
del self.poller
|
||||
|
||||
def registerSocket(self, SubSocket socket):
|
||||
self.sub_sockets.append(socket)
|
||||
self.poller.registerSocket(socket.socket)
|
||||
|
||||
def poll(self, timeout):
|
||||
sockets = []
|
||||
cdef int t = timeout
|
||||
|
||||
with nogil:
|
||||
result = self.poller.poll(t)
|
||||
|
||||
for s in result:
|
||||
socket = SubSocket()
|
||||
socket.setPtr(s)
|
||||
sockets.append(socket)
|
||||
|
||||
return sockets
|
||||
|
||||
cdef class SubSocket:
|
||||
cdef cppSubSocket * socket
|
||||
cdef bool is_owner
|
||||
|
||||
def __cinit__(self):
|
||||
self.socket = cppSubSocket.create()
|
||||
self.is_owner = True
|
||||
|
||||
if self.socket == NULL:
|
||||
raise MessagingError
|
||||
|
||||
def __dealloc__(self):
|
||||
if self.is_owner:
|
||||
del self.socket
|
||||
|
||||
cdef setPtr(self, cppSubSocket * ptr):
|
||||
if self.is_owner:
|
||||
del self.socket
|
||||
|
||||
self.is_owner = False
|
||||
self.socket = ptr
|
||||
|
||||
def connect(self, Context context, string endpoint, string address=b"127.0.0.1", bool conflate=False):
|
||||
r = self.socket.connect(context.context, endpoint, address, conflate)
|
||||
|
||||
if r != 0:
|
||||
if errno.errno == errno.EADDRINUSE:
|
||||
raise MultiplePublishersError
|
||||
else:
|
||||
raise MessagingError
|
||||
|
||||
def setTimeout(self, int timeout):
|
||||
self.socket.setTimeout(timeout)
|
||||
|
||||
def receive(self, bool non_blocking=False):
|
||||
msg = self.socket.receive(non_blocking)
|
||||
|
||||
if msg == NULL:
|
||||
# If a blocking read returns no message check errno if SIGINT was caught in the C++ code
|
||||
if errno.errno == errno.EINTR:
|
||||
print("SIGINT received, exiting")
|
||||
sys.exit(1)
|
||||
|
||||
return None
|
||||
else:
|
||||
sz = msg.getSize()
|
||||
m = msg.getData()[:sz]
|
||||
del msg
|
||||
|
||||
return m
|
||||
|
||||
|
||||
cdef class PubSocket:
|
||||
cdef cppPubSocket * socket
|
||||
|
||||
def __cinit__(self):
|
||||
self.socket = cppPubSocket.create()
|
||||
if self.socket == NULL:
|
||||
raise MessagingError
|
||||
|
||||
def __dealloc__(self):
|
||||
del self.socket
|
||||
|
||||
def connect(self, Context context, string endpoint):
|
||||
r = self.socket.connect(context.context, endpoint)
|
||||
|
||||
if r != 0:
|
||||
if errno.errno == errno.EADDRINUSE:
|
||||
raise MultiplePublishersError
|
||||
else:
|
||||
raise MessagingError
|
||||
|
||||
def send(self, string data):
|
||||
length = len(data)
|
||||
r = self.socket.send(<char*>data.c_str(), length)
|
||||
|
||||
if r != length:
|
||||
if errno.errno == errno.EADDRINUSE:
|
||||
raise MultiplePublishersError
|
||||
else:
|
||||
raise MessagingError
|
||||
@@ -1,56 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sysconfig
|
||||
from distutils.core import Extension, setup # pylint: disable=import-error,no-name-in-module
|
||||
|
||||
from Cython.Build import cythonize
|
||||
from Cython.Distutils import build_ext
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
sourcefiles = ['messaging_pyx.pyx']
|
||||
extra_compile_args = ["-std=c++11"]
|
||||
libraries = ['zmq']
|
||||
ARCH = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() # pylint: disable=unexpected-keyword-arg
|
||||
|
||||
if ARCH == "aarch64":
|
||||
extra_compile_args += ["-Wno-deprecated-register"]
|
||||
libraries += ['gnustl_shared']
|
||||
|
||||
setup(name='CAN parser',
|
||||
cmdclass={'build_ext': BuildExtWithoutPlatformSuffix},
|
||||
ext_modules=cythonize(
|
||||
Extension(
|
||||
"messaging_pyx",
|
||||
language="c++",
|
||||
sources=sourcefiles,
|
||||
extra_compile_args=extra_compile_args,
|
||||
libraries=libraries,
|
||||
extra_objects=[
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), '../', 'libmessaging.a'),
|
||||
]
|
||||
)
|
||||
),
|
||||
nthreads=4,
|
||||
)
|
||||
@@ -1,449 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <csignal>
|
||||
#include <random>
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "msgq.hpp"
|
||||
|
||||
void sigusr2_handler(int signal) {
|
||||
assert(signal == SIGUSR2);
|
||||
}
|
||||
|
||||
uint64_t msgq_get_uid(void){
|
||||
std::random_device rd("/dev/urandom");
|
||||
std::uniform_int_distribution<uint64_t> distribution(0,std::numeric_limits<uint32_t>::max());
|
||||
|
||||
uint64_t uid = distribution(rd) << 32 | syscall(SYS_gettid);
|
||||
return uid;
|
||||
}
|
||||
|
||||
int msgq_msg_init_size(msgq_msg_t * msg, size_t size){
|
||||
msg->size = size;
|
||||
msg->data = new(std::nothrow) char[size];
|
||||
|
||||
return (msg->data == NULL) ? -1 : 0;
|
||||
}
|
||||
|
||||
|
||||
int msgq_msg_init_data(msgq_msg_t * msg, char * data, size_t size) {
|
||||
int r = msgq_msg_init_size(msg, size);
|
||||
|
||||
if (r == 0)
|
||||
memcpy(msg->data, data, size);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int msgq_msg_close(msgq_msg_t * msg){
|
||||
if (msg->size > 0)
|
||||
delete[] msg->data;
|
||||
|
||||
msg->size = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void msgq_reset_reader(msgq_queue_t * q){
|
||||
int id = q->reader_id;
|
||||
q->read_valids[id]->store(true);
|
||||
q->read_pointers[id]->store(*q->write_pointer);
|
||||
}
|
||||
|
||||
void msgq_wait_for_subscriber(msgq_queue_t *q){
|
||||
while (*q->num_readers == 0){
|
||||
;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int msgq_new_queue(msgq_queue_t * q, const char * path, size_t size){
|
||||
assert(size < 0xFFFFFFFF); // Buffer must be smaller than 2^32 bytes
|
||||
|
||||
std::signal(SIGUSR2, sigusr2_handler);
|
||||
|
||||
const char * prefix = "/dev/shm/";
|
||||
char * full_path = new char[strlen(path) + strlen(prefix) + 1];
|
||||
strcpy(full_path, prefix);
|
||||
strcat(full_path, path);
|
||||
|
||||
auto fd = open(full_path, O_RDWR | O_CREAT, 0777);
|
||||
delete[] full_path;
|
||||
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
int rc = ftruncate(fd, size + sizeof(msgq_header_t));
|
||||
if (rc < 0)
|
||||
return -1;
|
||||
|
||||
char * mem = (char*)mmap(NULL, size + sizeof(msgq_header_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
close(fd);
|
||||
|
||||
if (mem == NULL)
|
||||
return -1;
|
||||
|
||||
q->mmap_p = mem;
|
||||
|
||||
msgq_header_t *header = (msgq_header_t *)mem;
|
||||
|
||||
// Setup pointers to header segment
|
||||
q->num_readers = reinterpret_cast<std::atomic<uint64_t>*>(&header->num_readers);
|
||||
q->write_pointer = reinterpret_cast<std::atomic<uint64_t>*>(&header->write_pointer);
|
||||
q->write_uid = reinterpret_cast<std::atomic<uint64_t>*>(&header->write_uid);
|
||||
|
||||
for (size_t i = 0; i < NUM_READERS; i++){
|
||||
q->read_pointers[i] = reinterpret_cast<std::atomic<uint64_t>*>(&header->read_pointers[i]);
|
||||
q->read_valids[i] = reinterpret_cast<std::atomic<uint64_t>*>(&header->read_valids[i]);
|
||||
q->read_uids[i] = reinterpret_cast<std::atomic<uint64_t>*>(&header->read_uids[i]);
|
||||
}
|
||||
|
||||
q->data = mem + sizeof(msgq_header_t);
|
||||
q->size = size;
|
||||
q->reader_id = -1;
|
||||
|
||||
q->endpoint = path;
|
||||
q->read_conflate = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void msgq_close_queue(msgq_queue_t *q){
|
||||
if (q->mmap_p != NULL){
|
||||
munmap(q->mmap_p, q->size + sizeof(msgq_header_t));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void msgq_init_publisher(msgq_queue_t * q) {
|
||||
//std::cout << "Starting publisher" << std::endl;
|
||||
uint64_t uid = msgq_get_uid();
|
||||
|
||||
*q->write_uid = uid;
|
||||
*q->num_readers = 0;
|
||||
|
||||
for (size_t i = 0; i < NUM_READERS; i++){
|
||||
*q->read_valids[i] = false;
|
||||
*q->read_uids[i] = 0;
|
||||
}
|
||||
|
||||
q->write_uid_local = uid;
|
||||
}
|
||||
|
||||
static void thread_signal(uint32_t tid) {
|
||||
#ifndef SYS_tkill
|
||||
// TODO: this won't work for multithreaded programs
|
||||
kill(tid, SIGUSR2);
|
||||
#else
|
||||
syscall(SYS_tkill, tid, SIGUSR2);
|
||||
#endif
|
||||
}
|
||||
|
||||
void msgq_init_subscriber(msgq_queue_t * q) {
|
||||
assert(q != NULL);
|
||||
assert(q->num_readers != NULL);
|
||||
|
||||
uint64_t uid = msgq_get_uid();
|
||||
|
||||
// Get reader id
|
||||
while (true){
|
||||
uint64_t cur_num_readers = *q->num_readers;
|
||||
uint64_t new_num_readers = cur_num_readers + 1;
|
||||
|
||||
// No more slots available. Reset all subscribers to kick out inactive ones
|
||||
if (new_num_readers > NUM_READERS){
|
||||
std::cout << "Warning, evicting all subscribers!" << std::endl;
|
||||
*q->num_readers = 0;
|
||||
|
||||
for (size_t i = 0; i < NUM_READERS; i++){
|
||||
*q->read_valids[i] = false;
|
||||
|
||||
uint64_t old_uid = *q->read_uids[i];
|
||||
*q->read_uids[i] = 0;
|
||||
|
||||
// Wake up reader in case they are in a poll
|
||||
thread_signal(old_uid & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use atomic compare and swap to handle race condition
|
||||
// where two subscribers start at the same time
|
||||
if (std::atomic_compare_exchange_strong(q->num_readers,
|
||||
&cur_num_readers,
|
||||
new_num_readers)){
|
||||
q->reader_id = cur_num_readers;
|
||||
q->read_uid_local = uid;
|
||||
|
||||
// We start with read_valid = false,
|
||||
// on the first read the read pointer will be synchronized with the write pointer
|
||||
*q->read_valids[cur_num_readers] = false;
|
||||
*q->read_pointers[cur_num_readers] = 0;
|
||||
*q->read_uids[cur_num_readers] = uid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//std::cout << "New subscriber id: " << q->reader_id << " uid: " << q->read_uid_local << " " << q->endpoint << std::endl;
|
||||
msgq_reset_reader(q);
|
||||
}
|
||||
|
||||
int msgq_msg_send(msgq_msg_t * msg, msgq_queue_t *q){
|
||||
// Die if we are no longer the active publisher
|
||||
if (q->write_uid_local != *q->write_uid){
|
||||
std::cout << "Killing old publisher: " << q->endpoint << std::endl;
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t total_msg_size = ALIGN(msg->size + sizeof(int64_t));
|
||||
|
||||
// We need to fit at least three messages in the queue,
|
||||
// then we can always safely access the last message
|
||||
assert(3 * total_msg_size <= q->size);
|
||||
|
||||
uint64_t num_readers = *q->num_readers;
|
||||
|
||||
uint32_t write_cycles, write_pointer;
|
||||
UNPACK64(write_cycles, write_pointer, *q->write_pointer);
|
||||
|
||||
char *p = q->data + write_pointer; // add base offset
|
||||
|
||||
// Check remaining space
|
||||
// Always leave space for a wraparound tag for the next message, including alignment
|
||||
int64_t remaining_space = q->size - write_pointer - total_msg_size - sizeof(int64_t);
|
||||
if (remaining_space <= 0){
|
||||
// Write -1 size tag indicating wraparound
|
||||
*(int64_t*)p = -1;
|
||||
|
||||
// Invalidate all readers that are beyond the write pointer
|
||||
// TODO: should we handle the case where a new reader shows up while this is running?
|
||||
for (uint64_t i = 0; i < num_readers; i++){
|
||||
uint64_t read_pointer = *q->read_pointers[i];
|
||||
uint64_t read_cycles = read_pointer >> 32;
|
||||
read_pointer &= 0xFFFFFFFF;
|
||||
|
||||
if ((read_pointer > write_pointer) && (read_cycles != write_cycles)) {
|
||||
*q->read_valids[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Update global and local copies of write pointer and write_cycles
|
||||
write_pointer = 0;
|
||||
write_cycles = write_cycles + 1;
|
||||
PACK64(*q->write_pointer, write_cycles, write_pointer);
|
||||
|
||||
// Set actual pointer to the beginning of the data segment
|
||||
p = q->data;
|
||||
}
|
||||
|
||||
// Invalidate readers that are in the area that will be written
|
||||
uint64_t start = write_pointer;
|
||||
uint64_t end = ALIGN(start + sizeof(int64_t) + msg->size);
|
||||
|
||||
for (uint64_t i = 0; i < num_readers; i++){
|
||||
uint32_t read_cycles, read_pointer;
|
||||
UNPACK64(read_cycles, read_pointer, *q->read_pointers[i]);
|
||||
|
||||
if ((read_pointer >= start) && (read_pointer < end) && (read_cycles != write_cycles)) {
|
||||
*q->read_valids[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Write size tag
|
||||
std::atomic<int64_t> *size_p = reinterpret_cast<std::atomic<int64_t>*>(p);
|
||||
*size_p = msg->size;
|
||||
|
||||
// Copy data
|
||||
memcpy(p + sizeof(int64_t), msg->data, msg->size);
|
||||
__sync_synchronize();
|
||||
|
||||
// Update write pointer
|
||||
uint32_t new_ptr = ALIGN(write_pointer + msg->size + sizeof(int64_t));
|
||||
PACK64(*q->write_pointer, write_cycles, new_ptr);
|
||||
|
||||
// Notify readers
|
||||
for (uint64_t i = 0; i < num_readers; i++){
|
||||
uint64_t reader_uid = *q->read_uids[i];
|
||||
thread_signal(reader_uid & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
return msg->size;
|
||||
}
|
||||
|
||||
|
||||
int msgq_msg_ready(msgq_queue_t * q){
|
||||
start:
|
||||
int id = q->reader_id;
|
||||
assert(id >= 0); // Make sure subscriber is initialized
|
||||
|
||||
if (q->read_uid_local != *q->read_uids[id]){
|
||||
std::cout << q->endpoint << ": Reader was evicted, reconnecting" << std::endl;
|
||||
msgq_init_subscriber(q);
|
||||
goto start;
|
||||
}
|
||||
|
||||
// Check valid
|
||||
if (!*q->read_valids[id]){
|
||||
msgq_reset_reader(q);
|
||||
goto start;
|
||||
}
|
||||
|
||||
uint32_t read_cycles, read_pointer;
|
||||
UNPACK64(read_cycles, read_pointer, *q->read_pointers[id]);
|
||||
|
||||
uint32_t write_cycles, write_pointer;
|
||||
UNPACK64(write_cycles, write_pointer, *q->write_pointer);
|
||||
|
||||
// Check if new message is available
|
||||
return (read_pointer != write_pointer);
|
||||
}
|
||||
|
||||
int msgq_msg_recv(msgq_msg_t * msg, msgq_queue_t * q){
|
||||
start:
|
||||
int id = q->reader_id;
|
||||
assert(id >= 0); // Make sure subscriber is initialized
|
||||
|
||||
if (q->read_uid_local != *q->read_uids[id]){
|
||||
std::cout << q->endpoint << ": Reader was evicted, reconnecting" << std::endl;
|
||||
msgq_init_subscriber(q);
|
||||
goto start;
|
||||
}
|
||||
|
||||
// Check valid
|
||||
if (!*q->read_valids[id]){
|
||||
msgq_reset_reader(q);
|
||||
goto start;
|
||||
}
|
||||
|
||||
uint32_t read_cycles, read_pointer;
|
||||
UNPACK64(read_cycles, read_pointer, *q->read_pointers[id]);
|
||||
|
||||
uint32_t write_cycles, write_pointer;
|
||||
UNPACK64(write_cycles, write_pointer, *q->write_pointer);
|
||||
|
||||
char * p = q->data + read_pointer;
|
||||
|
||||
// Check if new message is available
|
||||
if (read_pointer == write_pointer) {
|
||||
msg->size = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read potential message size
|
||||
std::atomic<int64_t> *size_p = reinterpret_cast<std::atomic<int64_t>*>(p);
|
||||
std::int64_t size = *size_p;
|
||||
|
||||
// Check if the size that was read is valid
|
||||
if (!*q->read_valids[id]){
|
||||
msgq_reset_reader(q);
|
||||
goto start;
|
||||
}
|
||||
|
||||
// If size is -1 the buffer was full, and we need to wrap around
|
||||
if (size == -1){
|
||||
read_cycles++;
|
||||
PACK64(*q->read_pointers[id], read_cycles, 0);
|
||||
goto start;
|
||||
}
|
||||
|
||||
// crashing is better than passing garbage data to the consumer
|
||||
// the size will have weird value if it was overwritten by data accidentally
|
||||
assert((uint64_t)size < q->size);
|
||||
assert(size > 0);
|
||||
|
||||
uint32_t new_read_pointer = ALIGN(read_pointer + sizeof(std::int64_t) + size);
|
||||
|
||||
// If conflate is true, check if this is the latest message, else start over
|
||||
if (q->read_conflate){
|
||||
if (new_read_pointer != write_pointer){
|
||||
// Update read pointer
|
||||
PACK64(*q->read_pointers[id], read_cycles, new_read_pointer);
|
||||
goto start;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy message
|
||||
if (msgq_msg_init_size(msg, size) < 0)
|
||||
return -1;
|
||||
|
||||
__sync_synchronize();
|
||||
memcpy(msg->data, p + sizeof(int64_t), size);
|
||||
__sync_synchronize();
|
||||
|
||||
// Update read pointer
|
||||
PACK64(*q->read_pointers[id], read_cycles, new_read_pointer);
|
||||
|
||||
// Check if the actual data that was copied is valid
|
||||
if (!*q->read_valids[id]){
|
||||
msgq_msg_close(msg);
|
||||
msgq_reset_reader(q);
|
||||
goto start;
|
||||
}
|
||||
|
||||
|
||||
return msg->size;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int msgq_poll(msgq_pollitem_t * items, size_t nitems, int timeout){
|
||||
assert(timeout >= 0);
|
||||
|
||||
int num = 0;
|
||||
|
||||
// Check if messages ready
|
||||
for (size_t i = 0; i < nitems; i++) {
|
||||
items[i].revents = msgq_msg_ready(items[i].q);
|
||||
if (items[i].revents) num++;
|
||||
}
|
||||
|
||||
int ms = (timeout == -1) ? 100 : timeout;
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000 * 1000;
|
||||
|
||||
|
||||
while (num == 0) {
|
||||
int ret;
|
||||
|
||||
ret = nanosleep(&ts, &ts);
|
||||
|
||||
// Check if messages ready
|
||||
for (size_t i = 0; i < nitems; i++) {
|
||||
if (items[i].revents == 0 && msgq_msg_ready(items[i].q)){
|
||||
num += 1;
|
||||
items[i].revents = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// exit if we had a timeout and the sleep finished
|
||||
if (timeout != -1 && ret == 0){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
|
||||
#define DEFAULT_SEGMENT_SIZE (10 * 1024 * 1024)
|
||||
#define NUM_READERS 8
|
||||
#define ALIGN(n) ((n + (8 - 1)) & -8)
|
||||
|
||||
#define UNPACK64(higher, lower, input) do {uint64_t tmp = input; higher = tmp >> 32; lower = tmp & 0xFFFFFFFF;} while (0)
|
||||
#define PACK64(output, higher, lower) output = ((uint64_t)higher << 32 ) | ((uint64_t)lower & 0xFFFFFFFF)
|
||||
|
||||
struct msgq_header_t {
|
||||
uint64_t num_readers;
|
||||
uint64_t write_pointer;
|
||||
uint64_t write_uid;
|
||||
uint64_t read_pointers[NUM_READERS];
|
||||
uint64_t read_valids[NUM_READERS];
|
||||
uint64_t read_uids[NUM_READERS];
|
||||
};
|
||||
|
||||
struct msgq_queue_t {
|
||||
std::atomic<uint64_t> *num_readers;
|
||||
std::atomic<uint64_t> *write_pointer;
|
||||
std::atomic<uint64_t> *write_uid;
|
||||
std::atomic<uint64_t> *read_pointers[NUM_READERS];
|
||||
std::atomic<uint64_t> *read_valids[NUM_READERS];
|
||||
std::atomic<uint64_t> *read_uids[NUM_READERS];
|
||||
char * mmap_p;
|
||||
char * data;
|
||||
size_t size;
|
||||
int reader_id;
|
||||
uint64_t read_uid_local;
|
||||
uint64_t write_uid_local;
|
||||
|
||||
bool read_conflate;
|
||||
std::string endpoint;
|
||||
};
|
||||
|
||||
struct msgq_msg_t {
|
||||
size_t size;
|
||||
char * data;
|
||||
};
|
||||
|
||||
struct msgq_pollitem_t {
|
||||
msgq_queue_t *q;
|
||||
int revents;
|
||||
};
|
||||
|
||||
void msgq_wait_for_subscriber(msgq_queue_t *q);
|
||||
void msgq_reset_reader(msgq_queue_t *q);
|
||||
|
||||
int msgq_msg_init_size(msgq_msg_t *msg, size_t size);
|
||||
int msgq_msg_init_data(msgq_msg_t *msg, char * data, size_t size);
|
||||
int msgq_msg_close(msgq_msg_t *msg);
|
||||
|
||||
int msgq_new_queue(msgq_queue_t * q, const char * path, size_t size);
|
||||
void msgq_close_queue(msgq_queue_t *q);
|
||||
void msgq_init_publisher(msgq_queue_t * q);
|
||||
void msgq_init_subscriber(msgq_queue_t * q);
|
||||
|
||||
int msgq_msg_send(msgq_msg_t *msg, msgq_queue_t *q);
|
||||
int msgq_msg_recv(msgq_msg_t *msg, msgq_queue_t *q);
|
||||
int msgq_msg_ready(msgq_queue_t * q);
|
||||
int msgq_poll(msgq_pollitem_t * items, size_t nitems, int timeout);
|
||||
@@ -1,56 +0,0 @@
|
||||
# MSGQ: A lock free single producer multi consumer message queue
|
||||
|
||||
[](https://dev.azure.com/commaai/default/_build/latest?definitionId=21&branchName=master)
|
||||
|
||||
## What is MSGQ?
|
||||
MSGQ is a system to pass messages from a single producer to multiple consumers. All the consumers need to be able to receive all the messages. It is designed to be a high performance replacement for ZMQ-like SUB/PUB patterns. It uses a ring buffer in shared memory to efficiently read and write data. Each read requires a copy. Writing can be done without a copy, as long as the size of the data is known in advance.
|
||||
|
||||
## Storage
|
||||
The storage for the queue consists of an area of metadata, and the actual buffer. The metadata contains:
|
||||
|
||||
1. A counter to the number of readers that are active
|
||||
2. A pointer to the head of the queue for writing. From now on referred to as *write pointer*
|
||||
3. A cycle counter for the writer. This counter is incremented when the writer wraps around
|
||||
4. N pointers, pointing to the current read position for all the readers. From now on referred to as *read pointer*
|
||||
5. N counters, counting the number of cycles for all the readers
|
||||
6. N booleans, indicating validity for all the readers. From now on referred to as *validity flag*
|
||||
|
||||
The counter and the pointer are both 32 bit values, packed into 64 bit so they can be read and written atomically.
|
||||
|
||||
The data buffer is a ring buffer. All messages are prefixed by an 8 byte size field, followed by the data. A size of -1 indicates a wrap-around, and means the next message is stored at the beginning of the buffer.
|
||||
|
||||
|
||||
## Writing
|
||||
Writing involves the following steps:
|
||||
|
||||
1. Check if the area that is to be written overlaps with any of the read pointers, mark those readers as invalid by clearing the validity flag.
|
||||
2. Write the message
|
||||
3. Increase the write pointer by the size of the message
|
||||
|
||||
In case there is not enough space at the end of the buffer, a special empty message with a prefix of -1 is written. The cycle counter is incremented by one. In this case step 1 will check there are no read pointers pointing to the remainder of the buffer. Then another write cycle will start with the actual message.
|
||||
|
||||
There always needs to be 8 bytes of empty space at the end of the buffer. By doing this there is always space to write the -1.
|
||||
|
||||
## Reset reader
|
||||
When the reader is lagging too much behind the read pointer becomes invalid and no longer points to the beginning of a valid message. To reset a reader to the current write pointer, the following steps are performed:
|
||||
|
||||
1. Set valid flag
|
||||
2. Set read cycle counter to that of the writer
|
||||
3. Set read pointer to write pointer
|
||||
|
||||
## Reading
|
||||
Reading involves the following steps:
|
||||
|
||||
1. Read the size field at the current read pointer
|
||||
2. Read the validity flag
|
||||
3. Copy the data out of the buffer
|
||||
4. Increase the read pointer by the size of the message
|
||||
5. Check the validity flag again
|
||||
|
||||
Before starting the copy, the valid flag is checked. This is to prevent a race condition where the size prefix was invalid, and the read could read outside of the buffer. Make sure that step 1 and 2 are not reordered by your compiler or CPU.
|
||||
|
||||
If a writer overwrites the data while it's being copied out, the data will be invalid. Therefore the validity flag is also checked after reading it. The order of step 4 and 5 does not matter.
|
||||
|
||||
If at steps 2 or 5 the validity flag is not set, the reader is reset. Any data that was already read is discarded. After the reader is reset, the reading starts from the beginning.
|
||||
|
||||
If a message with size -1 is encountered, step 3 and 4 are replaced by increasing the cycle counter and setting the read pointer to the beginning of the buffer. After that another read is performed.
|
||||
@@ -1,395 +0,0 @@
|
||||
#include "catch2/catch.hpp"
|
||||
#include "msgq.hpp"
|
||||
|
||||
TEST_CASE("ALIGN"){
|
||||
REQUIRE(ALIGN(0) == 0);
|
||||
REQUIRE(ALIGN(1) == 8);
|
||||
REQUIRE(ALIGN(7) == 8);
|
||||
REQUIRE(ALIGN(8) == 8);
|
||||
REQUIRE(ALIGN(99999) == 100000);
|
||||
}
|
||||
|
||||
TEST_CASE("msgq_msg_init_size"){
|
||||
const size_t msg_size = 30;
|
||||
msgq_msg_t msg;
|
||||
|
||||
msgq_msg_init_size(&msg, msg_size);
|
||||
REQUIRE(msg.size == msg_size);
|
||||
|
||||
msgq_msg_close(&msg);
|
||||
}
|
||||
|
||||
TEST_CASE("msgq_msg_init_data"){
|
||||
const size_t msg_size = 30;
|
||||
char * data = new char[msg_size];
|
||||
|
||||
for (size_t i = 0; i < msg_size; i++){
|
||||
data[i] = i;
|
||||
}
|
||||
|
||||
msgq_msg_t msg;
|
||||
msgq_msg_init_data(&msg, data, msg_size);
|
||||
|
||||
REQUIRE(msg.size == msg_size);
|
||||
REQUIRE(memcmp(msg.data, data, msg_size) == 0);
|
||||
|
||||
delete[] data;
|
||||
msgq_msg_close(&msg);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("msgq_init_subscriber"){
|
||||
remove("/dev/shm/test_queue");
|
||||
msgq_queue_t q;
|
||||
msgq_new_queue(&q, "test_queue", 1024);
|
||||
REQUIRE(*q.num_readers == 0);
|
||||
|
||||
q.reader_id = 1;
|
||||
*q.read_valids[0] = false;
|
||||
*q.read_pointers[0] = ((uint64_t)1 << 32);
|
||||
|
||||
*q.write_pointer = 255;
|
||||
|
||||
msgq_init_subscriber(&q);
|
||||
REQUIRE(q.read_conflate == false);
|
||||
REQUIRE(*q.read_valids[0] == true);
|
||||
REQUIRE((*q.read_pointers[0] >> 32) == 0);
|
||||
REQUIRE((*q.read_pointers[0] & 0xFFFFFFFF) == 255);
|
||||
}
|
||||
|
||||
TEST_CASE("msgq_msg_send first message"){
|
||||
remove("/dev/shm/test_queue");
|
||||
msgq_queue_t q;
|
||||
msgq_new_queue(&q, "test_queue", 1024);
|
||||
msgq_init_publisher(&q);
|
||||
|
||||
REQUIRE(*q.write_pointer == 0);
|
||||
|
||||
size_t msg_size = 128;
|
||||
|
||||
SECTION("Aligned message size"){
|
||||
}
|
||||
SECTION("Unaligned message size"){
|
||||
msg_size--;
|
||||
}
|
||||
|
||||
char * data = new char[msg_size];
|
||||
|
||||
for (size_t i = 0; i < msg_size; i++){
|
||||
data[i] = i;
|
||||
}
|
||||
|
||||
msgq_msg_t msg;
|
||||
msgq_msg_init_data(&msg, data, msg_size);
|
||||
|
||||
|
||||
msgq_msg_send(&msg, &q);
|
||||
REQUIRE(*(int64_t*)q.data == msg_size); // Check size tag
|
||||
REQUIRE(*q.write_pointer == 128 + sizeof(int64_t));
|
||||
REQUIRE(memcmp(q.data + sizeof(int64_t), data, msg_size) == 0);
|
||||
|
||||
delete[] data;
|
||||
msgq_msg_close(&msg);
|
||||
}
|
||||
|
||||
TEST_CASE("msgq_msg_send test wraparound"){
|
||||
remove("/dev/shm/test_queue");
|
||||
msgq_queue_t q;
|
||||
msgq_new_queue(&q, "test_queue", 1024);
|
||||
msgq_init_publisher(&q);
|
||||
|
||||
REQUIRE((*q.write_pointer & 0xFFFFFFFF) == 0);
|
||||
REQUIRE((*q.write_pointer >> 32) == 0);
|
||||
|
||||
const size_t msg_size = 120;
|
||||
msgq_msg_t msg;
|
||||
msgq_msg_init_size(&msg, msg_size);
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
msgq_msg_send(&msg, &q);
|
||||
}
|
||||
// Check 8th message was written at the beginning
|
||||
REQUIRE((*q.write_pointer & 0xFFFFFFFF) == msg_size + sizeof(int64_t));
|
||||
|
||||
// Check cycle count
|
||||
REQUIRE((*q.write_pointer >> 32) == 1);
|
||||
|
||||
// Check wraparound tag
|
||||
char * tag_location = q.data;
|
||||
tag_location += 7 * (msg_size + sizeof(int64_t));
|
||||
REQUIRE(*(int64_t*)tag_location == -1);
|
||||
|
||||
msgq_msg_close(&msg);
|
||||
}
|
||||
|
||||
TEST_CASE("msgq_msg_recv test wraparound"){
|
||||
remove("/dev/shm/test_queue");
|
||||
msgq_queue_t q_pub, q_sub;
|
||||
msgq_new_queue(&q_pub, "test_queue", 1024);
|
||||
msgq_new_queue(&q_sub, "test_queue", 1024);
|
||||
|
||||
msgq_init_publisher(&q_pub);
|
||||
msgq_init_subscriber(&q_sub);
|
||||
|
||||
REQUIRE((*q_pub.write_pointer >> 32) == 0);
|
||||
REQUIRE((*q_sub.read_pointers[0] >> 32) == 0);
|
||||
|
||||
const size_t msg_size = 120;
|
||||
msgq_msg_t msg1;
|
||||
msgq_msg_init_size(&msg1, msg_size);
|
||||
|
||||
|
||||
SECTION("Check cycle counter after reset") {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
msgq_msg_send(&msg1, &q_pub);
|
||||
}
|
||||
|
||||
msgq_msg_t msg2;
|
||||
msgq_msg_recv(&msg2, &q_sub);
|
||||
REQUIRE(msg2.size == 0); // Reader had to reset
|
||||
msgq_msg_close(&msg2);
|
||||
|
||||
}
|
||||
SECTION("Check cycle counter while keeping up with writer") {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
msgq_msg_send(&msg1, &q_pub);
|
||||
|
||||
msgq_msg_t msg2;
|
||||
msgq_msg_recv(&msg2, &q_sub);
|
||||
REQUIRE(msg2.size > 0);
|
||||
msgq_msg_close(&msg2);
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE((*q_sub.read_pointers[0] >> 32) == 1);
|
||||
msgq_msg_close(&msg1);
|
||||
}
|
||||
|
||||
TEST_CASE("msgq_msg_send test invalidation"){
|
||||
remove("/dev/shm/test_queue");
|
||||
msgq_queue_t q_pub, q_sub;
|
||||
msgq_new_queue(&q_pub, "test_queue", 1024);
|
||||
msgq_new_queue(&q_sub, "test_queue", 1024);
|
||||
|
||||
msgq_init_publisher(&q_pub);
|
||||
msgq_init_subscriber(&q_sub);
|
||||
*q_sub.write_pointer = (uint64_t)1 << 32;
|
||||
|
||||
REQUIRE(*q_sub.read_valids[0] == true);
|
||||
|
||||
SECTION("read pointer in tag"){
|
||||
*q_sub.read_pointers[0] = 0;
|
||||
}
|
||||
SECTION("read pointer in data section"){
|
||||
*q_sub.read_pointers[0] = 64;
|
||||
}
|
||||
SECTION("read pointer in wraparound section"){
|
||||
*q_pub.write_pointer = ((uint64_t)1 << 32) | 1000; // Writer is one cycle ahead
|
||||
*q_sub.read_pointers[0] = 1020;
|
||||
}
|
||||
|
||||
msgq_msg_t msg;
|
||||
msgq_msg_init_size(&msg, 128);
|
||||
msgq_msg_send(&msg, &q_pub);
|
||||
|
||||
REQUIRE(*q_sub.read_valids[0] == false);
|
||||
|
||||
msgq_msg_close(&msg);
|
||||
}
|
||||
|
||||
TEST_CASE("msgq_init_subscriber init 2 subscribers"){
|
||||
remove("/dev/shm/test_queue");
|
||||
msgq_queue_t q1, q2;
|
||||
msgq_new_queue(&q1, "test_queue", 1024);
|
||||
msgq_new_queue(&q2, "test_queue", 1024);
|
||||
|
||||
*q1.num_readers = 0;
|
||||
|
||||
REQUIRE(*q1.num_readers == 0);
|
||||
REQUIRE(*q2.num_readers == 0);
|
||||
|
||||
msgq_init_subscriber(&q1);
|
||||
REQUIRE(*q1.num_readers == 1);
|
||||
REQUIRE(*q2.num_readers == 1);
|
||||
REQUIRE(q1.reader_id == 0);
|
||||
|
||||
msgq_init_subscriber(&q2);
|
||||
REQUIRE(*q1.num_readers == 2);
|
||||
REQUIRE(*q2.num_readers == 2);
|
||||
REQUIRE(q2.reader_id == 1);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Write 1 msg, read 1 msg", "[integration]"){
|
||||
remove("/dev/shm/test_queue");
|
||||
const size_t msg_size = 128;
|
||||
msgq_queue_t writer, reader;
|
||||
|
||||
msgq_new_queue(&writer, "test_queue", 1024);
|
||||
msgq_new_queue(&reader, "test_queue", 1024);
|
||||
|
||||
msgq_init_publisher(&writer);
|
||||
msgq_init_subscriber(&reader);
|
||||
|
||||
// Build 128 byte message
|
||||
msgq_msg_t outgoing_msg;
|
||||
msgq_msg_init_size(&outgoing_msg, msg_size);
|
||||
|
||||
for (size_t i = 0; i < msg_size; i++){
|
||||
outgoing_msg.data[i] = i;
|
||||
}
|
||||
|
||||
REQUIRE(msgq_msg_send(&outgoing_msg, &writer) == msg_size);
|
||||
|
||||
msgq_msg_t incoming_msg1;
|
||||
REQUIRE(msgq_msg_recv(&incoming_msg1, &reader) == msg_size);
|
||||
REQUIRE(memcmp(incoming_msg1.data, outgoing_msg.data, msg_size) == 0);
|
||||
|
||||
// Verify that there are no more messages
|
||||
msgq_msg_t incoming_msg2;
|
||||
REQUIRE(msgq_msg_recv(&incoming_msg2, &reader) == 0);
|
||||
|
||||
msgq_msg_close(&outgoing_msg);
|
||||
msgq_msg_close(&incoming_msg1);
|
||||
msgq_msg_close(&incoming_msg2);
|
||||
}
|
||||
|
||||
TEST_CASE("Write 2 msg, read 2 msg - conflate = false", "[integration]"){
|
||||
remove("/dev/shm/test_queue");
|
||||
const size_t msg_size = 128;
|
||||
msgq_queue_t writer, reader;
|
||||
|
||||
msgq_new_queue(&writer, "test_queue", 1024);
|
||||
msgq_new_queue(&reader, "test_queue", 1024);
|
||||
|
||||
msgq_init_publisher(&writer);
|
||||
msgq_init_subscriber(&reader);
|
||||
|
||||
// Build 128 byte message
|
||||
msgq_msg_t outgoing_msg;
|
||||
msgq_msg_init_size(&outgoing_msg, msg_size);
|
||||
|
||||
for (size_t i = 0; i < msg_size; i++){
|
||||
outgoing_msg.data[i] = i;
|
||||
}
|
||||
|
||||
REQUIRE(msgq_msg_send(&outgoing_msg, &writer) == msg_size);
|
||||
REQUIRE(msgq_msg_send(&outgoing_msg, &writer) == msg_size);
|
||||
|
||||
msgq_msg_t incoming_msg1;
|
||||
REQUIRE(msgq_msg_recv(&incoming_msg1, &reader) == msg_size);
|
||||
REQUIRE(memcmp(incoming_msg1.data, outgoing_msg.data, msg_size) == 0);
|
||||
|
||||
msgq_msg_t incoming_msg2;
|
||||
REQUIRE(msgq_msg_recv(&incoming_msg2, &reader) == msg_size);
|
||||
REQUIRE(memcmp(incoming_msg2.data, outgoing_msg.data, msg_size) == 0);
|
||||
|
||||
msgq_msg_close(&outgoing_msg);
|
||||
msgq_msg_close(&incoming_msg1);
|
||||
msgq_msg_close(&incoming_msg2);
|
||||
}
|
||||
|
||||
TEST_CASE("Write 2 msg, read 2 msg - conflate = true", "[integration]"){
|
||||
remove("/dev/shm/test_queue");
|
||||
const size_t msg_size = 128;
|
||||
msgq_queue_t writer, reader;
|
||||
|
||||
msgq_new_queue(&writer, "test_queue", 1024);
|
||||
msgq_new_queue(&reader, "test_queue", 1024);
|
||||
|
||||
msgq_init_publisher(&writer);
|
||||
msgq_init_subscriber(&reader);
|
||||
reader.read_conflate = true;
|
||||
|
||||
// Build 128 byte message
|
||||
msgq_msg_t outgoing_msg;
|
||||
msgq_msg_init_size(&outgoing_msg, msg_size);
|
||||
|
||||
for (size_t i = 0; i < msg_size; i++){
|
||||
outgoing_msg.data[i] = i;
|
||||
}
|
||||
|
||||
REQUIRE(msgq_msg_send(&outgoing_msg, &writer) == msg_size);
|
||||
REQUIRE(msgq_msg_send(&outgoing_msg, &writer) == msg_size);
|
||||
|
||||
msgq_msg_t incoming_msg1;
|
||||
REQUIRE(msgq_msg_recv(&incoming_msg1, &reader) == msg_size);
|
||||
REQUIRE(memcmp(incoming_msg1.data, outgoing_msg.data, msg_size) == 0);
|
||||
|
||||
// Verify that there are no more messages
|
||||
msgq_msg_t incoming_msg2;
|
||||
REQUIRE(msgq_msg_recv(&incoming_msg2, &reader) == 0);
|
||||
|
||||
msgq_msg_close(&outgoing_msg);
|
||||
msgq_msg_close(&incoming_msg1);
|
||||
msgq_msg_close(&incoming_msg2);
|
||||
}
|
||||
|
||||
TEST_CASE("1 publisher, 1 slow subscriber", "[integration]"){
|
||||
remove("/dev/shm/test_queue");
|
||||
msgq_queue_t writer, reader;
|
||||
|
||||
msgq_new_queue(&writer, "test_queue", 1024);
|
||||
msgq_new_queue(&reader, "test_queue", 1024);
|
||||
|
||||
msgq_init_publisher(&writer);
|
||||
msgq_init_subscriber(&reader);
|
||||
|
||||
int n_received = 0;
|
||||
int n_skipped = 0;
|
||||
|
||||
for (uint64_t i = 0; i < 1e5; i++) {
|
||||
msgq_msg_t outgoing_msg;
|
||||
msgq_msg_init_data(&outgoing_msg, (char*)&i, sizeof(uint64_t));
|
||||
msgq_msg_send(&outgoing_msg, &writer);
|
||||
msgq_msg_close(&outgoing_msg);
|
||||
|
||||
if (i % 10 == 0){
|
||||
msgq_msg_t msg1;
|
||||
msgq_msg_recv(&msg1, &reader);
|
||||
|
||||
if (msg1.size == 0){
|
||||
n_skipped++;
|
||||
} else {
|
||||
n_received++;
|
||||
}
|
||||
msgq_msg_close(&msg1);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: verify these numbers by hand
|
||||
REQUIRE(n_received == 8572);
|
||||
REQUIRE(n_skipped == 1428);
|
||||
}
|
||||
|
||||
TEST_CASE("1 publisher, 2 subscribers", "[integration]"){
|
||||
remove("/dev/shm/test_queue");
|
||||
msgq_queue_t writer, reader1, reader2;
|
||||
|
||||
msgq_new_queue(&writer, "test_queue", 1024);
|
||||
msgq_new_queue(&reader1, "test_queue", 1024);
|
||||
msgq_new_queue(&reader2, "test_queue", 1024);
|
||||
|
||||
msgq_init_publisher(&writer);
|
||||
msgq_init_subscriber(&reader1);
|
||||
msgq_init_subscriber(&reader2);
|
||||
|
||||
for (uint64_t i = 0; i < 1024 * 3; i++) {
|
||||
msgq_msg_t outgoing_msg;
|
||||
msgq_msg_init_data(&outgoing_msg, (char*)&i, sizeof(uint64_t));
|
||||
msgq_msg_send(&outgoing_msg, &writer);
|
||||
|
||||
msgq_msg_t msg1, msg2;
|
||||
msgq_msg_recv(&msg1, &reader1);
|
||||
msgq_msg_recv(&msg2, &reader2);
|
||||
|
||||
REQUIRE(msg1.size == sizeof(uint64_t));
|
||||
REQUIRE(msg2.size == sizeof(uint64_t));
|
||||
REQUIRE(*(uint64_t*)msg1.data == i);
|
||||
REQUIRE(*(uint64_t*)msg2.data == i);
|
||||
|
||||
msgq_msg_close(&outgoing_msg);
|
||||
msgq_msg_close(&msg1);
|
||||
msgq_msg_close(&msg2);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
from messaging_pyx import Context, SubSocket, PubSocket # pylint: disable=no-name-in-module, import-error
|
||||
|
||||
if __name__ == "__main__":
|
||||
c = Context()
|
||||
pub_sock = PubSocket()
|
||||
pub_sock.connect(c, "controlsState")
|
||||
|
||||
for i in range(int(1e10)):
|
||||
print(i)
|
||||
sub_sock = SubSocket()
|
||||
sub_sock.connect(c, "controlsState")
|
||||
|
||||
pub_sock.send(b'a')
|
||||
print(sub_sock.receive())
|
||||
@@ -1,2 +0,0 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch2/catch.hpp"
|
||||
@@ -1,142 +0,0 @@
|
||||
import unittest
|
||||
import time
|
||||
import cereal.messaging as messaging
|
||||
|
||||
import concurrent.futures
|
||||
|
||||
|
||||
def poller():
|
||||
context = messaging.Context()
|
||||
|
||||
p = messaging.Poller()
|
||||
|
||||
sub = messaging.SubSocket()
|
||||
sub.connect(context, 'controlsState')
|
||||
p.registerSocket(sub)
|
||||
|
||||
socks = p.poll(10000)
|
||||
r = [s.receive(non_blocking=True) for s in socks]
|
||||
|
||||
return r
|
||||
|
||||
|
||||
class TestPoller(unittest.TestCase):
|
||||
def test_poll_once(self):
|
||||
context = messaging.Context()
|
||||
|
||||
pub = messaging.PubSocket()
|
||||
pub.connect(context, 'controlsState')
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as e:
|
||||
poll = e.submit(poller)
|
||||
|
||||
time.sleep(0.1) # Slow joiner syndrome
|
||||
|
||||
# Send message
|
||||
pub.send("a")
|
||||
|
||||
# Wait for poll result
|
||||
result = poll.result()
|
||||
|
||||
del pub
|
||||
context.term()
|
||||
|
||||
self.assertEqual(result, [b"a"])
|
||||
|
||||
def test_poll_and_create_many_subscribers(self):
|
||||
context = messaging.Context()
|
||||
|
||||
pub = messaging.PubSocket()
|
||||
pub.connect(context, 'controlsState')
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as e:
|
||||
poll = e.submit(poller)
|
||||
|
||||
time.sleep(0.1) # Slow joiner syndrome
|
||||
c = messaging.Context()
|
||||
for _ in range(10):
|
||||
messaging.SubSocket().connect(c, 'controlsState')
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
# Send message
|
||||
pub.send("a")
|
||||
|
||||
# Wait for poll result
|
||||
result = poll.result()
|
||||
|
||||
del pub
|
||||
context.term()
|
||||
|
||||
self.assertEqual(result, [b"a"])
|
||||
|
||||
def test_multiple_publishers_exception(self):
|
||||
context = messaging.Context()
|
||||
|
||||
with self.assertRaises(messaging.MultiplePublishersError):
|
||||
pub1 = messaging.PubSocket()
|
||||
pub1.connect(context, 'controlsState')
|
||||
|
||||
pub2 = messaging.PubSocket()
|
||||
pub2.connect(context, 'controlsState')
|
||||
|
||||
pub1.send("a")
|
||||
|
||||
del pub1
|
||||
del pub2
|
||||
context.term()
|
||||
|
||||
def test_multiple_messages(self):
|
||||
context = messaging.Context()
|
||||
|
||||
pub = messaging.PubSocket()
|
||||
pub.connect(context, 'controlsState')
|
||||
|
||||
sub = messaging.SubSocket()
|
||||
sub.connect(context, 'controlsState')
|
||||
|
||||
time.sleep(0.1) # Slow joiner
|
||||
|
||||
for i in range(100):
|
||||
pub.send(str(i))
|
||||
|
||||
msg_seen = False
|
||||
i = 0
|
||||
while True:
|
||||
r = sub.receive(non_blocking=True)
|
||||
|
||||
if r is not None:
|
||||
self.assertEqual(str(i), r.decode('utf8'))
|
||||
|
||||
msg_seen = True
|
||||
i += 1
|
||||
|
||||
if r is None and msg_seen: # ZMQ sometimes receives nothing on the first receive
|
||||
break
|
||||
|
||||
del pub
|
||||
del sub
|
||||
context.term()
|
||||
|
||||
def test_conflate(self):
|
||||
context = messaging.Context()
|
||||
|
||||
pub = messaging.PubSocket()
|
||||
pub.connect(context, 'controlsState')
|
||||
|
||||
sub = messaging.SubSocket()
|
||||
sub.connect(context, 'controlsState', conflate=True)
|
||||
|
||||
time.sleep(0.1) # Slow joiner
|
||||
pub.send('a')
|
||||
pub.send('b')
|
||||
|
||||
self.assertEqual(b'b', sub.receive())
|
||||
|
||||
del pub
|
||||
del sub
|
||||
context.term()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,164 +0,0 @@
|
||||
# TODO: these port numbers are hardcoded in c, fix this
|
||||
|
||||
# LogRotate: 8001 is a PUSH PULL socket between loggerd and visiond
|
||||
|
||||
# all ZMQ pub sub: port, should_log, frequency, (qlog_decimation)
|
||||
|
||||
# frame syncing packet
|
||||
frame: [8002, true, 20., 1]
|
||||
# accel, gyro, and compass
|
||||
sensorEvents: [8003, true, 100., 100]
|
||||
# GPS data, also global timestamp
|
||||
gpsNMEA: [8004, true, 9.] # 9 msgs each sec
|
||||
# CPU+MEM+GPU+BAT temps
|
||||
thermal: [8005, true, 2., 1]
|
||||
# List(CanData), list of can messages
|
||||
can: [8006, true, 100.]
|
||||
controlsState: [8007, true, 100., 100]
|
||||
#liveEvent: [8008, true, 0.]
|
||||
model: [8009, true, 20., 5]
|
||||
features: [8010, true, 0.]
|
||||
health: [8011, true, 2., 1]
|
||||
radarState: [8012, true, 20.]
|
||||
#liveUI: [8014, true, 0.]
|
||||
encodeIdx: [8015, true, 20.]
|
||||
liveTracks: [8016, true, 20.]
|
||||
sendcan: [8017, true, 100.]
|
||||
logMessage: [8018, true, 0.]
|
||||
liveCalibration: [8019, true, 4., 4]
|
||||
androidLog: [8020, true, 0.]
|
||||
carState: [8021, true, 100., 10]
|
||||
# 8022 is reserved for sshd
|
||||
carControl: [8023, true, 100., 10]
|
||||
plan: [8024, true, 20.]
|
||||
liveLocation: [8025, true, 0., 1]
|
||||
gpsLocation: [8026, true, 1., 1]
|
||||
ethernetData: [8027, true, 0.]
|
||||
navUpdate: [8028, true, 0.]
|
||||
qcomGnss: [8029, true, 0.]
|
||||
lidarPts: [8030, true, 0.]
|
||||
procLog: [8031, true, 0.5]
|
||||
gpsLocationExternal: [8032, true, 10., 1]
|
||||
ubloxGnss: [8033, true, 10.]
|
||||
clocks: [8034, true, 1., 1]
|
||||
liveMpc: [8035, false, 20.]
|
||||
liveLongitudinalMpc: [8036, false, 20.]
|
||||
navStatus: [8038, true, 0.]
|
||||
gpsLocationTrimble: [8039, true, 0.]
|
||||
trimbleGnss: [8041, true, 0.]
|
||||
ubloxRaw: [8042, true, 20.]
|
||||
gpsPlannerPoints: [8043, true, 0.]
|
||||
gpsPlannerPlan: [8044, true, 0.]
|
||||
applanixRaw: [8046, true, 0.]
|
||||
orbLocation: [8047, true, 0.]
|
||||
trafficEvents: [8048, true, 0.]
|
||||
liveLocationTiming: [8049, true, 0.]
|
||||
orbslamCorrection: [8050, true, 0.]
|
||||
liveLocationCorrected: [8051, true, 0.]
|
||||
orbObservation: [8052, true, 0.]
|
||||
applanixLocation: [8053, true, 0.]
|
||||
liveLocationKalman: [8054, true, 0.]
|
||||
uiNavigationEvent: [8055, true, 0.]
|
||||
orbOdometry: [8057, true, 0.]
|
||||
orbFeatures: [8058, false, 0.]
|
||||
orbKeyFrame: [8059, true, 0.]
|
||||
uiLayoutState: [8060, true, 0.]
|
||||
frontEncodeIdx: [8061, true, 5.]
|
||||
orbFeaturesSummary: [8062, true, 0.]
|
||||
driverState: [8063, true, 5., 1]
|
||||
liveParameters: [8064, true, 10.]
|
||||
liveMapData: [8065, true, 0.]
|
||||
cameraOdometry: [8066, true, 20., 5]
|
||||
pathPlan: [8067, true, 20.]
|
||||
kalmanOdometry: [8068, true, 0.]
|
||||
thumbnail: [8069, true, 0.2, 1]
|
||||
carEvents: [8070, true, 1., 1]
|
||||
carParams: [8071, true, 0.02, 1]
|
||||
frontFrame: [8072, true, 10.]
|
||||
dMonitoringState: [8073, true, 5., 1]
|
||||
|
||||
testModel: [8040, false, 0.]
|
||||
testLiveLocation: [8045, false, 0.]
|
||||
testJoystick: [8056, false, 0.]
|
||||
|
||||
# 8080 is reserved for slave testing daemon
|
||||
# 8762 is reserved for logserver
|
||||
|
||||
# manager -- base process to manage starting and stopping of all others
|
||||
# subscribes: thermal
|
||||
|
||||
# **** processes that communicate with the outside world ****
|
||||
|
||||
# thermald -- decides when to start and stop onroad
|
||||
# subscribes: health, location
|
||||
# publishes: thermal
|
||||
|
||||
# boardd -- communicates with the car
|
||||
# subscribes: sendcan
|
||||
# publishes: can, health, ubloxRaw
|
||||
|
||||
# sensord -- publishes IMU and Magnetometer
|
||||
# publishes: sensorEvents
|
||||
|
||||
# gpsd -- publishes EON's gps
|
||||
# publishes: gpsNMEA
|
||||
|
||||
# visiond -- talks to the cameras, runs the model, saves the videos
|
||||
# publishes: frame, model, driverMonitoring, cameraOdometry, thumbnail
|
||||
|
||||
# **** stateful data transformers ****
|
||||
|
||||
# plannerd -- decides where to drive the car
|
||||
# subscribes: carState, model, radarState, controlsState, liveParameters
|
||||
# publishes: plan, pathPlan, liveMpc, liveLongitudinalMpc
|
||||
|
||||
# controlsd -- drives the car by sending CAN messages to panda
|
||||
# subscribes: can, thermal, health, plan, pathPlan, driverMonitoring, liveCalibration
|
||||
# publishes: carState, carControl, sendcan, controlsState, carEvents, carParams
|
||||
|
||||
# radard -- processes the radar and vision data
|
||||
# subscribes: can, controlsState, model, liveParameters
|
||||
# publishes: radarState, liveTracks
|
||||
|
||||
# params_learner -- learns vehicle params by observing the vehicle dynamics
|
||||
# subscribes: controlsState, sensorEvents, cameraOdometry
|
||||
# publishes: liveParameters
|
||||
|
||||
# calibrationd -- reads posenet and applies a temporal filter on the frame region to look at
|
||||
# subscribes: cameraOdometry
|
||||
# publishes: liveCalibration
|
||||
|
||||
# ubloxd -- read raw ublox data and converts them in readable format
|
||||
# subscribes: ubloxRaw
|
||||
# publishes: ubloxGnss
|
||||
|
||||
# **** LOGGING SERVICE ****
|
||||
|
||||
# loggerd
|
||||
# subscribes: EVERYTHING
|
||||
|
||||
# **** NON VITAL SERVICES ****
|
||||
|
||||
# ui
|
||||
# subscribes: thermal, model, controlsState, uiLayout, liveCalibration, radarState, liveMpc, plusFrame, liveMapData
|
||||
|
||||
# uploader
|
||||
# communicates through file system with loggerd
|
||||
|
||||
# deleter
|
||||
# communicates through file system with loggerd and uploader
|
||||
|
||||
# logmessaged -- central logging service, can log to cloud
|
||||
# publishes: logMessage
|
||||
|
||||
# logcatd -- fetches logcat info from android
|
||||
# publishes: androidLog
|
||||
|
||||
# proclogd -- fetches process information
|
||||
# publishes: procLog
|
||||
|
||||
# tombstoned -- reports native crashes
|
||||
|
||||
# athenad -- on request, open a sub socket and return the value
|
||||
|
||||
# updated -- waits for network access and tries to update every hour
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import yaml
|
||||
|
||||
class Service():
|
||||
def __init__(self, port, should_log, frequency, decimation=None):
|
||||
self.port = port
|
||||
self.should_log = should_log
|
||||
self.frequency = frequency
|
||||
self.decimation = decimation
|
||||
|
||||
service_list_path = os.path.join(os.path.dirname(__file__), "service_list.yaml")
|
||||
|
||||
service_list = {}
|
||||
with open(service_list_path, "r") as f:
|
||||
for k, v in yaml.safe_load(f).items():
|
||||
decimation = None
|
||||
if len(v) == 4:
|
||||
decimation = v[3]
|
||||
|
||||
service_list[k] = Service(v[0], v[1], v[2], decimation)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("/* THIS IS AN AUTOGENERATED FILE, PLEASE EDIT service_list.yaml */")
|
||||
print("#ifndef __SERVICES_H")
|
||||
print("#define __SERVICES_H")
|
||||
print("struct service { int port; bool should_log; int frequency; int decimation; char name[0x100]; };")
|
||||
print("static struct service services[] = {")
|
||||
for k, v in service_list.items():
|
||||
print(' { .name = "%s", .port = %d, .should_log = %s, .frequency = %d, .decimation = %d },' % (k, v.port, "true" if v.should_log else "false", v.frequency, -1 if v.decimation is None else v.decimation))
|
||||
print("};")
|
||||
print("#endif")
|
||||
|
||||
+2
-1
@@ -33,9 +33,10 @@ def start_frame():
|
||||
def set_package_permissions():
|
||||
pm_grant("ai.comma.plus.offroad", "android.permission.ACCESS_FINE_LOCATION")
|
||||
pm_grant("ai.comma.plus.offroad", "android.permission.READ_PHONE_STATE")
|
||||
pm_grant("ai.comma.plus.offroad", "android.permission.READ_EXTERNAL_STORAGE")
|
||||
appops_set("ai.comma.plus.offroad", "SU", "allow")
|
||||
appops_set("ai.comma.plus.offroad", "WIFI_SCAN", "allow")
|
||||
appops_set("ai.comma.plus.offroad", "READ_EXTERNAL_STORAGE", "allow")
|
||||
appops_set("ai.comma.plus.offroad", "WRITE_EXTERNAL_STORAGE", "allow")
|
||||
|
||||
def appops_set(package, op, mode):
|
||||
system(f"LD_LIBRARY_PATH= appops set {package} {op} {mode}")
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import collections
|
||||
from contextlib import closing
|
||||
|
||||
from common.file_helpers import mkdirs_exists_ok
|
||||
|
||||
class ColumnStoreReader():
|
||||
def __init__(self, path, mmap=False, allow_pickle=False, direct_io=False):
|
||||
if not (path and os.path.isdir(path)):
|
||||
raise ValueError("Not a column store: {}".format(path))
|
||||
|
||||
self._path = os.path.realpath(path)
|
||||
self._keys = os.listdir(self._path)
|
||||
self._mmap = mmap
|
||||
self._allow_pickle = allow_pickle
|
||||
self._direct_io = direct_io
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._path
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def get(self, key):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def keys(self):
|
||||
return list(self._keys)
|
||||
|
||||
def iteritems(self):
|
||||
for k in self:
|
||||
yield (k, self[k])
|
||||
|
||||
def itervalues(self):
|
||||
for k in self:
|
||||
yield self[k]
|
||||
|
||||
def get_npy_path(self, key):
|
||||
"""Gets a filesystem path for an npy file containing the specified array,
|
||||
or none if the column store does not contain key.
|
||||
"""
|
||||
if key in self:
|
||||
return os.path.join(self._path, key)
|
||||
else:
|
||||
return None
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
path = os.path.join(self._path, key)
|
||||
|
||||
# TODO(mgraczyk): This implementation will need to change for zip.
|
||||
if os.path.isdir(path):
|
||||
return ColumnStoreReader(path)
|
||||
else:
|
||||
if self._mmap:
|
||||
# note that direct i/o does nothing for mmap since file read/write interface is not used
|
||||
ret = np.load(path, mmap_mode='r', allow_pickle=self._allow_pickle, fix_imports=False)
|
||||
else:
|
||||
if self._direct_io:
|
||||
opener = lambda path, flags: os.open(path, os.O_RDONLY | os.O_DIRECT)
|
||||
with open(path, 'rb', buffering=0, opener=opener) as f:
|
||||
ret = np.load(f, allow_pickle=self._allow_pickle, fix_imports=False)
|
||||
else:
|
||||
ret = np.load(path, allow_pickle=self._allow_pickle, fix_imports=False)
|
||||
if type(ret) == np.lib.npyio.NpzFile:
|
||||
# if it's saved as compressed, it has arr_0 only in the file. deref this
|
||||
return ret['arr_0']
|
||||
else:
|
||||
return ret
|
||||
except IOError:
|
||||
raise KeyError(key)
|
||||
|
||||
def __contains__(self, item):
|
||||
try:
|
||||
self[item]
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def __len__(self):
|
||||
return len(self._keys)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self._keys)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._keys)
|
||||
|
||||
def __str__(self):
|
||||
return "ColumnStoreReader({})".format(str({k: "..." for k in self._keys}))
|
||||
|
||||
def __enter__(self): return self
|
||||
def __exit__(self, type, value, traceback): self.close()
|
||||
|
||||
class ColumnStoreWriter():
|
||||
def __init__(self, path, allow_pickle=False):
|
||||
self._path = path
|
||||
self._allow_pickle = allow_pickle
|
||||
mkdirs_exists_ok(self._path)
|
||||
|
||||
def map_column(self, path, dtype, shape):
|
||||
npy_path = os.path.join(self._path, path)
|
||||
mkdirs_exists_ok(os.path.dirname(npy_path))
|
||||
return np.lib.format.open_memmap(npy_path, mode='w+', dtype=dtype, shape=shape)
|
||||
|
||||
def add_column(self, path, data, dtype=None, compression=False, overwrite=False):
|
||||
npy_path = os.path.join(self._path, path)
|
||||
mkdirs_exists_ok(os.path.dirname(npy_path))
|
||||
|
||||
if overwrite:
|
||||
f = open(npy_path, "wb")
|
||||
else:
|
||||
f = os.fdopen(os.open(npy_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL), "wb")
|
||||
|
||||
with closing(f) as f:
|
||||
data2 = np.array(data, copy=False, dtype=dtype)
|
||||
if compression:
|
||||
np.savez_compressed(f, data2)
|
||||
else:
|
||||
np.save(f, data2, allow_pickle=self._allow_pickle, fix_imports=False)
|
||||
|
||||
def add_group(self, group_name):
|
||||
# TODO(mgraczyk): This implementation will need to change if we add zip or compression.
|
||||
return ColumnStoreWriter(os.path.join(self._path, group_name))
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def __enter__(self): return self
|
||||
def __exit__(self, type, value, traceback): self.close()
|
||||
|
||||
|
||||
def _save_dict_as_column_store(values, writer):
|
||||
for k, v in values.items():
|
||||
if isinstance(v, collections.Mapping):
|
||||
_save_dict_as_column_store(v, writer.add_group(k))
|
||||
else:
|
||||
writer.add_column(k, v)
|
||||
|
||||
|
||||
def save_dict_as_column_store(values, output_path):
|
||||
with ColumnStoreWriter(output_path) as writer:
|
||||
_save_dict_as_column_store(values, writer)
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# py2,3 compatiblity helpers
|
||||
|
||||
basestring = (str, bytes)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import numpy as np
|
||||
import os
|
||||
import csv
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
# locally cache cities from
|
||||
# https://github.com/thampiman/reverse-geocoder
|
||||
csv_file_name = path + '/rg_cities1000.csv'
|
||||
|
||||
# right hand drive is when the steering wheel is on the right of the car
|
||||
# left hand traffic is when cars driver on the left side of the road
|
||||
LHT_COUNTRIES = ['AU', 'IN', 'IE', 'JP', 'MU', 'MY', 'NZ', 'UK', 'ZA']
|
||||
|
||||
|
||||
def get_city(lat, lon):
|
||||
cities = np.array(list(csv.reader(open(csv_file_name))))[1:]
|
||||
positions = cities[:,:2].astype(np.float32)
|
||||
idx = np.argmin(np.linalg.norm((positions - np.array([lat, lon])), axis=1))
|
||||
return cities[idx]
|
||||
|
||||
|
||||
def is_lht(lat, lon):
|
||||
city = get_city(lat, lon)
|
||||
country = city[-1]
|
||||
return country in LHT_COUNTRIES
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
class lazy_property():
|
||||
"""Defines a property whose value will be computed only once and as needed.
|
||||
|
||||
This can only be used on instance methods.
|
||||
"""
|
||||
def __init__(self, func):
|
||||
self._func = func
|
||||
|
||||
def __get__(self, obj_self, cls):
|
||||
value = self._func(obj_self)
|
||||
setattr(obj_self, self._func.__name__, value)
|
||||
return value
|
||||
@@ -0,0 +1,284 @@
|
||||
import os
|
||||
import struct
|
||||
import bisect
|
||||
import numpy as np
|
||||
import _io
|
||||
|
||||
import capnp
|
||||
from cereal import log as capnp_log
|
||||
|
||||
class RawData():
|
||||
def __init__(self, f):
|
||||
self.f = _io.FileIO(f, 'rb')
|
||||
self.lenn = struct.unpack("I", self.f.read(4))[0]
|
||||
self.count = os.path.getsize(f) / (self.lenn+4)
|
||||
|
||||
def read(self, i):
|
||||
self.f.seek((self.lenn+4)*i + 4)
|
||||
return self.f.read(self.lenn)
|
||||
|
||||
def yuv420_to_rgb(raw, image_dim=None, swizzled=False):
|
||||
def expand(x):
|
||||
x = np.repeat(x, 2, axis=0)
|
||||
return np.repeat(x, 2, axis=1)
|
||||
|
||||
if image_dim is None:
|
||||
image_dim = (raw.shape[1]*2, raw.shape[2]*2)
|
||||
swizzled = True
|
||||
|
||||
if not swizzled:
|
||||
img_data = np.array(raw, copy=False, dtype=np.uint8)
|
||||
uv_len = (image_dim[0]/2)*(image_dim[1]/2)
|
||||
img_data_u = expand(img_data[image_dim[0]*image_dim[1]: \
|
||||
image_dim[0]*image_dim[1]+uv_len]. \
|
||||
reshape(image_dim[0]/2, image_dim[1]/2))
|
||||
|
||||
img_data_v = expand(img_data[image_dim[0]*image_dim[1]+uv_len: \
|
||||
image_dim[0]*image_dim[1]+2*uv_len]. \
|
||||
reshape(image_dim[0]/2, image_dim[1]/2))
|
||||
img_data_y = img_data[0:image_dim[0]*image_dim[1]].reshape(image_dim)
|
||||
else:
|
||||
img_data_y = np.zeros(image_dim, dtype=np.uint8)
|
||||
img_data_y[0::2, 0::2] = raw[0]
|
||||
img_data_y[1::2, 0::2] = raw[1]
|
||||
img_data_y[0::2, 1::2] = raw[2]
|
||||
img_data_y[1::2, 1::2] = raw[3]
|
||||
img_data_u = expand(raw[4])
|
||||
img_data_v = expand(raw[5])
|
||||
|
||||
yuv = np.stack((img_data_y, img_data_u, img_data_v)).swapaxes(0,2).swapaxes(0,1)
|
||||
yuv = yuv.astype(np.int16)
|
||||
|
||||
# http://maxsharabayko.blogspot.com/2016/01/fast-yuv-to-rgb-conversion-in-python-3.html
|
||||
# according to ITU-R BT.709
|
||||
yuv[:,:, 0] = yuv[:,:, 0].clip(16, 235).astype(yuv.dtype) - 16
|
||||
yuv[:,:,1:] = yuv[:,:,1:].clip(16, 240).astype(yuv.dtype) - 128
|
||||
|
||||
A = np.array([[1.164, 0.000, 1.793],
|
||||
[1.164, -0.213, -0.533],
|
||||
[1.164, 2.112, 0.000]])
|
||||
|
||||
# our result
|
||||
img = np.dot(yuv, A.T).clip(0, 255).astype('uint8')
|
||||
return img
|
||||
|
||||
|
||||
class YuvData():
|
||||
def __init__(self, f, dim=(160,320)):
|
||||
self.f = _io.FileIO(f, 'rb')
|
||||
self.image_dim = dim
|
||||
self.image_size = self.image_dim[0]/2 * self.image_dim[1]/2 * 6
|
||||
self.count = os.path.getsize(f) / self.image_size
|
||||
|
||||
def read_frame(self, frame):
|
||||
self.f.seek(self.image_size*frame)
|
||||
raw = self.f.read(self.image_size)
|
||||
return raw
|
||||
|
||||
def read_frames(self, range_start, range_len):
|
||||
self.f.seek(self.image_size*range_start)
|
||||
raw = self.f.read(self.image_size*range_len)
|
||||
return raw
|
||||
|
||||
def read_frames_into(self, range_start, buf):
|
||||
self.f.seek(self.image_size*range_start)
|
||||
return self.f.readinto(buf)
|
||||
|
||||
def read(self, frame):
|
||||
return yuv420_to_rgb(self.read_frame(frame), self.image_dim)
|
||||
|
||||
def close(self):
|
||||
self.f.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.close()
|
||||
|
||||
|
||||
|
||||
class OneReader():
|
||||
def __init__(self, base_path, goofy=False, segment_range=None):
|
||||
self.base_path = base_path
|
||||
|
||||
route_name = os.path.basename(base_path)
|
||||
|
||||
self.rcamera_size = (304, 560)
|
||||
|
||||
if segment_range is None:
|
||||
parent_path = os.path.dirname(base_path)
|
||||
self.segment_nums = []
|
||||
for p in os.listdir(parent_path):
|
||||
if not p.startswith(route_name+"--"):
|
||||
continue
|
||||
self.segment_nums.append(int(p.rsplit("--", 1)[-1]))
|
||||
if not self.segment_nums:
|
||||
raise Exception("no route segments found")
|
||||
self.segment_nums.sort()
|
||||
self.segment_range = (self.segment_nums[0], self.segment_nums[-1])
|
||||
else:
|
||||
self.segment_range = segment_range
|
||||
self.segment_nums = range(segment_range[0], segment_range[1]+1)
|
||||
for i in self.segment_nums:
|
||||
if not os.path.exists(base_path+"--"+str(i)):
|
||||
raise Exception("missing segment in provided range")
|
||||
|
||||
# goofy data is broken with discontinuous logs
|
||||
if goofy and (self.segment_range[0] != 0
|
||||
or self.segment_nums != range(self.segment_range[0], self.segment_range[1]+1)):
|
||||
raise Exception("goofy data needs all the segments for a route")
|
||||
|
||||
self.cur_seg = None
|
||||
self.cur_seg_f = None
|
||||
|
||||
# index the frames
|
||||
print("indexing frames {}...".format(self.segment_nums))
|
||||
|
||||
self.rcamera_encode_map = {} # frame_id -> (segment num, segment id, frame_time)
|
||||
last_frame_id = -1
|
||||
|
||||
if goofy:
|
||||
# goofy is goofy
|
||||
|
||||
frame_size = self.rcamera_size[0]*self.rcamera_size[1]*3/2
|
||||
|
||||
# find the encode id ranges for each segment by using the rcamera file size
|
||||
segment_encode_ids = []
|
||||
cur_encode_id = 0
|
||||
for n in self.segment_nums:
|
||||
camera_path = os.path.join(self.seg_path(n), "rcamera")
|
||||
if not os.path.exists(camera_path):
|
||||
# for goofy, missing camera files means a bad route
|
||||
raise Exception("Missing camera file {}".format(camera_path))
|
||||
camera_size = os.path.getsize(camera_path)
|
||||
assert (camera_size % frame_size) == 0
|
||||
|
||||
num_frames = camera_size / frame_size
|
||||
segment_encode_ids.append(cur_encode_id)
|
||||
cur_encode_id += num_frames
|
||||
|
||||
last_encode_id = -1
|
||||
# use the segment encode id map and frame events to build the frame index
|
||||
for n in self.segment_nums:
|
||||
log_path = os.path.join(self.seg_path(n), "rlog")
|
||||
if os.path.exists(log_path):
|
||||
with open(log_path, "rb") as f:
|
||||
for evt in capnp_log.Event.read_multiple(f):
|
||||
if evt.which() == 'frame':
|
||||
|
||||
if evt.frame.frameId < last_frame_id:
|
||||
# a non-increasing frame id is bad route (eg visiond was restarted)
|
||||
raise Exception("non-increasing frame id")
|
||||
last_frame_id = evt.frame.frameId
|
||||
|
||||
seg_i = bisect.bisect_right(segment_encode_ids, evt.frame.encodeId)-1
|
||||
assert seg_i >= 0
|
||||
seg_num = self.segment_nums[seg_i]
|
||||
seg_id = evt.frame.encodeId-segment_encode_ids[seg_i]
|
||||
frame_time = evt.logMonoTime / 1.0e9
|
||||
|
||||
self.rcamera_encode_map[evt.frame.frameId] = (seg_num, seg_id,
|
||||
frame_time)
|
||||
|
||||
last_encode_id = evt.frame.encodeId
|
||||
|
||||
if last_encode_id-cur_encode_id > 10:
|
||||
# too many missing frames is a bad route (eg route from before encoder rotating worked)
|
||||
raise Exception("goofy route is missing frames: {}, {}".format(
|
||||
last_encode_id, cur_encode_id))
|
||||
|
||||
else:
|
||||
# for harry data, build the index from encodeIdx events
|
||||
for n in self.segment_nums:
|
||||
log_path = os.path.join(self.seg_path(n), "rlog")
|
||||
if os.path.exists(log_path):
|
||||
with open(log_path, "rb") as f:
|
||||
for evt in capnp_log.Event.read_multiple(f):
|
||||
if evt.which() == 'encodeIdx' and evt.encodeIdx.type == 'bigBoxLossless':
|
||||
frame_time = evt.logMonoTime / 1.0e9
|
||||
self.rcamera_encode_map[evt.encodeIdx.frameId] = (
|
||||
evt.encodeIdx.segmentNum, evt.encodeIdx.segmentId,
|
||||
frame_time)
|
||||
|
||||
print("done")
|
||||
|
||||
# read the first event to find the start time
|
||||
self.reset_to_seg(self.segment_range[0])
|
||||
for evt in self.events():
|
||||
if evt.which() != 'initData':
|
||||
self.start_mono = evt.logMonoTime
|
||||
break
|
||||
self.reset_to_seg(self.segment_range[0])
|
||||
|
||||
|
||||
def seg_path(self, num):
|
||||
return self.base_path+"--"+str(num)
|
||||
|
||||
def reset_to_seg(self, seg):
|
||||
self.cur_seg = seg
|
||||
if self.cur_seg_f:
|
||||
self.cur_seg_f.close()
|
||||
self.cur_seg_f = None
|
||||
|
||||
def seek_ts(self, ts):
|
||||
seek_seg = int(ts/60)
|
||||
if seek_seg < self.segment_range[0] or seek_seg > self.segment_range[1]:
|
||||
raise ValueError
|
||||
|
||||
self.reset_to_seg(seek_seg)
|
||||
target_mono = self.start_mono + int(ts*1e9)
|
||||
for evt in self.events():
|
||||
if evt.logMonoTime >= target_mono:
|
||||
break
|
||||
|
||||
def read_event(self):
|
||||
while True:
|
||||
if self.cur_seg > self.segment_range[1]:
|
||||
return None
|
||||
if self.cur_seg_f is None:
|
||||
log_path = os.path.join(self.seg_path(self.cur_seg), "rlog")
|
||||
if not os.path.exists(log_path):
|
||||
print("missing log file!", log_path)
|
||||
self.cur_seg += 1
|
||||
continue
|
||||
self.cur_seg_f = open(log_path, "rb")
|
||||
|
||||
try:
|
||||
return capnp_log.Event.read(self.cur_seg_f)
|
||||
except capnp.lib.capnp.KjException as e:
|
||||
if 'EOF' in str(e): # dumb, but pycapnp does this too
|
||||
self.cur_seg_f.close()
|
||||
self.cur_seg_f = None
|
||||
self.cur_seg += 1
|
||||
else:
|
||||
raise
|
||||
|
||||
def events(self):
|
||||
while True:
|
||||
r = self.read_event()
|
||||
if r is None:
|
||||
break
|
||||
yield r
|
||||
|
||||
def read_frame(self, frame_id):
|
||||
encode_idx = self.rcamera_encode_map.get(frame_id)
|
||||
if encode_idx is None:
|
||||
return None
|
||||
|
||||
seg_num, seg_id, _ = encode_idx
|
||||
camera_path = os.path.join(self.seg_path(seg_num), "rcamera")
|
||||
if not os.path.exists(camera_path):
|
||||
return None
|
||||
with YuvData(camera_path, self.rcamera_size) as data:
|
||||
return data.read_frame(seg_id)
|
||||
|
||||
def close(self):
|
||||
if self.cur_seg_f is not None:
|
||||
self.cur_seg_f.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.close()
|
||||
@@ -0,0 +1,92 @@
|
||||
import sys
|
||||
import json
|
||||
# pip2 install msgpack-python
|
||||
import msgpack
|
||||
import zlib
|
||||
import os
|
||||
import logging
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
ext = ".gz"
|
||||
SWAG = '\xde\xe2\x11\x15VVC\xf2\x8ep\xd7\xe4\x87\x8d,9'
|
||||
|
||||
def compress_json(in_file, out_file):
|
||||
logging.debug("compressing %s -> %s", in_file, out_file)
|
||||
|
||||
errors = 0
|
||||
|
||||
good = []
|
||||
last_can_time = 0
|
||||
with open(in_file, 'r') as inf:
|
||||
for ln in inf:
|
||||
ln = ln.rstrip()
|
||||
if not ln: continue
|
||||
try:
|
||||
ll = json.loads(ln)
|
||||
except ValueError:
|
||||
errors += 1
|
||||
continue
|
||||
if ll is None or ll[0] is None:
|
||||
continue
|
||||
if ll[0][1] == 1:
|
||||
# no CAN in hex
|
||||
ll[1][2] = ll[1][2].decode("hex")
|
||||
# relativize the CAN timestamps
|
||||
this_can_time = ll[1][1]
|
||||
ll[1] = [ll[1][0], this_can_time - last_can_time, ll[1][2]]
|
||||
last_can_time = this_can_time
|
||||
good.append(ll)
|
||||
|
||||
logging.debug("compressing %s -> %s, read done", in_file, out_file)
|
||||
data = msgpack.packb(good)
|
||||
data_compressed = zlib.compress(data)
|
||||
# zlib doesn't care about this
|
||||
data_compressed += "\x00" * (16 - len(data_compressed)%16)
|
||||
aes = AES.new(SWAG, AES.MODE_CBC, "\x00"*16)
|
||||
data_encrypted = aes.encrypt(data_compressed)
|
||||
with open(out_file, "wb") as outf:
|
||||
outf.write(data_encrypted)
|
||||
|
||||
logging.debug("compressing %s -> %s, write done", in_file, out_file)
|
||||
|
||||
return errors
|
||||
|
||||
def decompress_json_internal(data_encrypted):
|
||||
aes = AES.new(SWAG, AES.MODE_CBC, "\x00"*16)
|
||||
data_compressed = aes.decrypt(data_encrypted)
|
||||
data = zlib.decompress(data_compressed)
|
||||
msgs = msgpack.unpackb(data)
|
||||
good = []
|
||||
last_can_time = 0
|
||||
for ll in msgs:
|
||||
if ll[0][1] == 1:
|
||||
# back into hex
|
||||
ll[1][2] = ll[1][2].encode("hex")
|
||||
# derelativize CAN timestamps
|
||||
last_can_time += ll[1][1]
|
||||
ll[1] = [ll[1][0], last_can_time, ll[1][2]]
|
||||
good.append(ll)
|
||||
return good
|
||||
|
||||
def decompress_json(in_file, out_file):
|
||||
logging.debug("decompressing %s -> %s", in_file, out_file)
|
||||
f = open(in_file)
|
||||
data_encrypted = f.read()
|
||||
f.close()
|
||||
|
||||
good = decompress_json_internal(data_encrypted)
|
||||
out = '\n'.join(map(lambda x: json.dumps(x), good)) + "\n"
|
||||
logging.debug("decompressing %s -> %s, writing", in_file, out_file)
|
||||
f = open(out_file, 'w')
|
||||
f.write(out)
|
||||
f.close()
|
||||
logging.debug("decompressing %s -> %s, write finished", in_file, out_file)
|
||||
|
||||
if __name__ == "__main__":
|
||||
for dat in sys.argv[1:]:
|
||||
print(dat)
|
||||
compress_json(dat, "/tmp/out"+ext)
|
||||
decompress_json("/tmp/out"+ext, "/tmp/test")
|
||||
os.system("diff "+dat+" /tmp/test")
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import bisect
|
||||
import numpy as np
|
||||
from scipy.interpolate import interp1d
|
||||
|
||||
|
||||
def deep_interp_0_fast(dx, x, y):
|
||||
FIX = False
|
||||
if len(y.shape) == 1:
|
||||
y = y[:, None]
|
||||
FIX = True
|
||||
ret = np.zeros((dx.shape[0], y.shape[1]))
|
||||
index = list(x)
|
||||
for i in range(dx.shape[0]):
|
||||
idx = bisect.bisect_left(index, dx[i])
|
||||
if idx == x.shape[0]:
|
||||
idx = x.shape[0] - 1
|
||||
ret[i] = y[idx]
|
||||
|
||||
if FIX:
|
||||
return ret[:, 0]
|
||||
else:
|
||||
return ret
|
||||
|
||||
|
||||
def running_mean(x, N):
|
||||
cumsum = np.cumsum(np.insert(x, [0]*(int(N/2)) + [-1]*(N-int(N/2)), [x[0]]*int(N/2) + [x[-1]]*(N-int(N/2))))
|
||||
return (cumsum[N:] - cumsum[:-N]) / N
|
||||
|
||||
|
||||
def deep_interp_np(x, xp, fp):
|
||||
x = np.atleast_1d(x)
|
||||
xp = np.array(xp)
|
||||
if len(xp) < 2:
|
||||
return np.repeat(fp, len(x), axis=0)
|
||||
if min(np.diff(xp)) < 0:
|
||||
raise RuntimeError('Bad x array for interpolation')
|
||||
j = np.searchsorted(xp, x) - 1
|
||||
j = np.clip(j, 0, len(xp)-2)
|
||||
d = np.divide(x - xp[j], xp[j + 1] - xp[j], out=np.ones_like(x, dtype=np.float64), where=xp[j + 1] - xp[j] != 0)
|
||||
vals_interp = (fp[j].T*(1 - d)).T + (fp[j + 1].T*d).T
|
||||
if len(vals_interp) == 1:
|
||||
return vals_interp[0]
|
||||
else:
|
||||
return vals_interp
|
||||
|
||||
|
||||
def clipping_deep_interp(x, xp, fp):
|
||||
if len(xp) < 2:
|
||||
return deep_interp_np(x, xp, fp)
|
||||
bad_idx = np.where(np.diff(xp) < 0)[0]
|
||||
if len(bad_idx) > 0:
|
||||
if bad_idx[0] ==1:
|
||||
return np.zeros([] + list(fp.shape[1:]))
|
||||
return deep_interp_np(x, xp[:bad_idx[0]], fp[:bad_idx[0]])
|
||||
else:
|
||||
return deep_interp_np(x, xp, fp)
|
||||
|
||||
|
||||
def deep_interp(dx, x, y, kind="slinear"):
|
||||
return interp1d(
|
||||
x, y,
|
||||
axis=0,
|
||||
kind=kind,
|
||||
bounds_error=False,
|
||||
fill_value="extrapolate",
|
||||
assume_sorted=True)(dx)
|
||||
@@ -81,7 +81,6 @@ keys = {
|
||||
"LiveParameters": [TxType.PERSISTENT],
|
||||
"LongitudinalControl": [TxType.PERSISTENT],
|
||||
"OpenpilotEnabledToggle": [TxType.PERSISTENT],
|
||||
"LaneChangeEnabled": [TxType.PERSISTENT],
|
||||
"PandaFirmware": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
"PandaFirmwareHex": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
"PandaDongleId": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
@@ -94,7 +93,6 @@ keys = {
|
||||
"TermsVersion": [TxType.PERSISTENT],
|
||||
"TrainingVersion": [TxType.PERSISTENT],
|
||||
"UpdateAvailable": [TxType.CLEAR_ON_MANAGER_START],
|
||||
"UpdateFailedCount": [TxType.CLEAR_ON_MANAGER_START],
|
||||
"Version": [TxType.PERSISTENT],
|
||||
"Offroad_ChargeDisabled": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT],
|
||||
"Offroad_ConnectivityNeeded": [TxType.CLEAR_ON_MANAGER_START],
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
# Copyright (C) 2016 Sixten Bergman
|
||||
# License WTFPL
|
||||
#
|
||||
# This program is free software. It comes without any warranty, to the extent
|
||||
# permitted by applicable law.
|
||||
# You can redistribute it and/or modify it under the terms of the Do What The
|
||||
# Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See
|
||||
# http://www.wtfpl.net/ for more details.
|
||||
#
|
||||
# note that the function peakdetect is derived from code which was released to
|
||||
# public domain see: http://billauer.co.il/peakdet.html
|
||||
#
|
||||
|
||||
from math import log
|
||||
import numpy as np
|
||||
|
||||
__all__ = ["peakdetect"]
|
||||
|
||||
|
||||
|
||||
def _datacheck_peakdetect(x_axis, y_axis):
|
||||
if x_axis is None:
|
||||
x_axis = range(len(y_axis))
|
||||
|
||||
if len(y_axis) != len(x_axis):
|
||||
raise ValueError("Input vectors y_axis and x_axis must have same length")
|
||||
|
||||
#needs to be a numpy array
|
||||
y_axis = np.array(y_axis)
|
||||
x_axis = np.array(x_axis)
|
||||
return x_axis, y_axis
|
||||
|
||||
|
||||
def _pad(fft_data, pad_len):
|
||||
"""
|
||||
Pads fft data to interpolate in time domain
|
||||
|
||||
keyword arguments:
|
||||
fft_data -- the fft
|
||||
pad_len -- By how many times the time resolution should be increased by
|
||||
|
||||
return: padded list
|
||||
"""
|
||||
length = len(fft_data)
|
||||
n = _n(length * pad_len)
|
||||
fft_data = list(fft_data)
|
||||
|
||||
return fft_data[:length // 2] + [0] * (2**n-length) + fft_data[length // 2:]
|
||||
|
||||
|
||||
def _n(x):
|
||||
"""
|
||||
Find the smallest value for n, which fulfils 2**n >= x
|
||||
|
||||
keyword arguments:
|
||||
x -- the value, which 2**n must surpass
|
||||
return: the integer n
|
||||
"""
|
||||
return int(log(x)/log(2)) + 1
|
||||
|
||||
|
||||
def peakdetect(y_axis, x_axis=None, lookahead=200, delta=0):
|
||||
"""
|
||||
Converted from/based on a MATLAB script at:
|
||||
http://billauer.co.il/peakdet.html
|
||||
function for detecting local maxima and minima in a signal.
|
||||
Discovers peaks by searching for values which are surrounded by lower
|
||||
or larger values for maxima and minima respectively
|
||||
keyword arguments:
|
||||
y_axis -- A list containing the signal over which to find peaks
|
||||
x_axis -- A x-axis whose values correspond to the y_axis list and is used
|
||||
in the return to specify the position of the peaks. If omitted an
|
||||
index of the y_axis is used.
|
||||
(default: None)
|
||||
lookahead -- distance to look ahead from a peak candidate to determine if
|
||||
it is the actual peak
|
||||
(default: 200)
|
||||
'(samples / period) / f' where '4 >= f >= 1.25' might be a good value
|
||||
delta -- this specifies a minimum difference between a peak and
|
||||
the following points, before a peak may be considered a peak. Useful
|
||||
to hinder the function from picking up false peaks towards to end of
|
||||
the signal. To work well delta should be set to delta >= RMSnoise * 5.
|
||||
(default: 0)
|
||||
When omitted delta function causes a 20% decrease in speed.
|
||||
When used Correctly it can double the speed of the function
|
||||
return: two lists [max_peaks, min_peaks] containing the positive and
|
||||
negative peaks respectively. Each cell of the lists contains a tuple
|
||||
of: (position, peak_value)
|
||||
to get the average peak value do: np.mean(max_peaks, 0)[1] on the
|
||||
results to unpack one of the lists into x, y coordinates do:
|
||||
x, y = zip(*max_peaks)
|
||||
"""
|
||||
max_peaks = []
|
||||
min_peaks = []
|
||||
dump = [] # Used to pop the first hit which almost always is false
|
||||
# check input data
|
||||
x_axis, y_axis = _datacheck_peakdetect(x_axis, y_axis)
|
||||
# store data length for later use
|
||||
length = len(y_axis)
|
||||
#perform some checks
|
||||
if lookahead < 1:
|
||||
raise ValueError("Lookahead must be '1' or above in value")
|
||||
if not (np.isscalar(delta) and delta >= 0):
|
||||
raise ValueError("delta must be a positive number")
|
||||
#maxima and minima candidates are temporarily stored in
|
||||
#mx and mn respectively
|
||||
mn, mx = np.Inf, -np.Inf
|
||||
#Only detect peak if there is 'lookahead' amount of points after it
|
||||
for index, (x, y) in enumerate(zip(x_axis[:-lookahead],
|
||||
y_axis[:-lookahead])):
|
||||
if y > mx:
|
||||
mx = y
|
||||
mxpos = x
|
||||
if y < mn:
|
||||
mn = y
|
||||
mnpos = x
|
||||
####look for max####
|
||||
if y < mx-delta and mx != np.Inf:
|
||||
#Maxima peak candidate found
|
||||
#look ahead in signal to ensure that this is a peak and not jitter
|
||||
if y_axis[index:index+lookahead].max() < mx:
|
||||
max_peaks.append([mxpos, mx])
|
||||
dump.append(True)
|
||||
#set algorithm to only find minima now
|
||||
mx = np.Inf
|
||||
mn = np.Inf
|
||||
if index+lookahead >= length:
|
||||
#end is within lookahead no more peaks can be found
|
||||
break
|
||||
continue
|
||||
#else: #slows shit down this does
|
||||
# mx = ahead
|
||||
# mxpos = x_axis[np.where(y_axis[index:index+lookahead]==mx)]
|
||||
####look for min####
|
||||
if y > mn+delta and mn != -np.Inf:
|
||||
#Minima peak candidate found
|
||||
#look ahead in signal to ensure that this is a peak and not jitter
|
||||
if y_axis[index:index+lookahead].min() > mn:
|
||||
min_peaks.append([mnpos, mn])
|
||||
dump.append(False)
|
||||
#set algorithm to only find maxima now
|
||||
mn = -np.Inf
|
||||
mx = -np.Inf
|
||||
if index+lookahead >= length:
|
||||
#end is within lookahead no more peaks can be found
|
||||
break
|
||||
#else: #slows shit down this does
|
||||
# mn = ahead
|
||||
# mnpos = x_axis[np.where(y_axis[index:index+lookahead]==mn)]
|
||||
#Remove the false hit on the first value of the y_axis
|
||||
try:
|
||||
if dump[0]:
|
||||
max_peaks.pop(0)
|
||||
else:
|
||||
min_peaks.pop(0)
|
||||
del dump
|
||||
except IndexError:
|
||||
#no peaks were found, should the function return empty lists?
|
||||
pass
|
||||
return [max_peaks, min_peaks]
|
||||
@@ -0,0 +1,78 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import random
|
||||
|
||||
class SamplingBuffer():
|
||||
def __init__(self, fn, size, write=False):
|
||||
self._fn = fn
|
||||
self._write = write
|
||||
if self._write:
|
||||
self._f = open(self._fn, "ab")
|
||||
else:
|
||||
self._f = open(self._fn, "rb")
|
||||
self._size = size
|
||||
self._refresh()
|
||||
|
||||
def _refresh(self):
|
||||
self.cnt = os.path.getsize(self._fn) / self._size
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
self._refresh()
|
||||
return self.cnt
|
||||
|
||||
def _fetch_one(self, x):
|
||||
assert self._write == False
|
||||
self._f.seek(x*self._size)
|
||||
return self._f.read(self._size)
|
||||
|
||||
def sample(self, count, indices = None):
|
||||
if indices == None:
|
||||
cnt = self.count
|
||||
assert cnt != 0
|
||||
indices = map(lambda x: random.randint(0, cnt-1), range(count))
|
||||
return map(self._fetch_one, indices)
|
||||
|
||||
def write(self, dat):
|
||||
assert self._write == True
|
||||
assert (len(dat) % self._size) == 0
|
||||
self._f.write(dat)
|
||||
self._f.flush()
|
||||
|
||||
class NumpySamplingBuffer():
|
||||
def __init__(self, fn, size, dtype, write=False):
|
||||
self._size = size
|
||||
self._dtype = dtype
|
||||
self._buf = SamplingBuffer(fn, len(np.zeros(size, dtype=dtype).tobytes()), write)
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return self._buf.count
|
||||
|
||||
def write(self, dat):
|
||||
self._buf.write(dat.tobytes())
|
||||
|
||||
def sample(self, count, indices = None):
|
||||
return np.fromstring(''.join(self._buf.sample(count, indices)), dtype=self._dtype).reshape([count]+list(self._size))
|
||||
|
||||
# TODO: n IOPS needed where n is the Multi
|
||||
class MultiNumpySamplingBuffer():
|
||||
def __init__(self, fn, npa, write=False):
|
||||
self._bufs = []
|
||||
for i,n in enumerate(npa):
|
||||
self._bufs.append(NumpySamplingBuffer(fn + ("_%d" % i), n[0], n[1], write))
|
||||
|
||||
def write(self, dat):
|
||||
for b,x in zip(self._bufs, dat):
|
||||
b.write(x)
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return min(map(lambda x: x.count, self._bufs))
|
||||
|
||||
def sample(self, count):
|
||||
cnt = self.count
|
||||
assert cnt != 0
|
||||
indices = map(lambda x: random.randint(0, cnt-1), range(count))
|
||||
return map(lambda x: x.sample(count, indices), self._bufs)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import numpy as np
|
||||
|
||||
_DESC_FMT = """
|
||||
{} (n={}):
|
||||
MEAN={}
|
||||
VAR={}
|
||||
MIN={}
|
||||
MAX={}
|
||||
"""
|
||||
|
||||
class StatTracker():
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
self._mean = 0.
|
||||
self._var = 0.
|
||||
self._n = 0
|
||||
self._min = -float("-inf")
|
||||
self._max = -float("inf")
|
||||
|
||||
@property
|
||||
def mean(self):
|
||||
return self._mean
|
||||
|
||||
@property
|
||||
def var(self):
|
||||
return (self._n * self._var) / (self._n - 1.)
|
||||
|
||||
@property
|
||||
def min(self):
|
||||
return self._min
|
||||
|
||||
@property
|
||||
def max(self):
|
||||
return self._max
|
||||
|
||||
def update(self, samples):
|
||||
# https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Parallel_algorithm
|
||||
data = samples.reshape(-1)
|
||||
n_a = data.size
|
||||
mean_a = np.mean(data)
|
||||
var_a = np.var(data, ddof=0)
|
||||
|
||||
n_b = self._n
|
||||
mean_b = self._mean
|
||||
|
||||
delta = mean_b - mean_a
|
||||
m_a = var_a * (n_a - 1)
|
||||
m_b = self._var * (n_b - 1)
|
||||
m2 = m_a + m_b + delta**2 * n_a * n_b / (n_a + n_b)
|
||||
|
||||
self._var = m2 / (n_a + n_b)
|
||||
self._mean = (n_a * mean_a + n_b * mean_b) / (n_a + n_b)
|
||||
self._n = n_a + n_b
|
||||
|
||||
self._min = min(self._min, np.min(data))
|
||||
self._max = max(self._max, np.max(data))
|
||||
|
||||
def __str__(self):
|
||||
return _DESC_FMT.format(self._name, self._n, self._mean, self.var, self._min,
|
||||
self._max)
|
||||
|
||||
# FIXME(mgraczyk): The variance computation does not work with 1 sample batches.
|
||||
class VectorStatTracker(StatTracker):
|
||||
def __init__(self, name, dim):
|
||||
self._name = name
|
||||
self._mean = np.zeros((dim, ))
|
||||
self._var = np.zeros((dim, dim))
|
||||
self._n = 0
|
||||
self._min = np.full((dim, ), -float("-inf"))
|
||||
self._max = np.full((dim, ), -float("inf"))
|
||||
|
||||
@property
|
||||
def cov(self):
|
||||
return self.var
|
||||
|
||||
def update(self, samples):
|
||||
n_a = samples.shape[0]
|
||||
mean_a = np.mean(samples, axis=0)
|
||||
var_a = np.cov(samples, ddof=0, rowvar=False)
|
||||
|
||||
n_b = self._n
|
||||
mean_b = self._mean
|
||||
|
||||
delta = mean_b - mean_a
|
||||
m_a = var_a * (n_a - 1)
|
||||
m_b = self._var * (n_b - 1)
|
||||
m2 = m_a + m_b + delta**2 * n_a * n_b / (n_a + n_b)
|
||||
|
||||
self._var = m2 / (n_a + n_b)
|
||||
self._mean = (n_a * mean_a + n_b * mean_b) / (n_a + n_b)
|
||||
self._n = n_a + n_b
|
||||
|
||||
self._min = np.minimum(self._min, np.min(samples, axis=0))
|
||||
self._max = np.maximum(self._max, np.max(samples, axis=0))
|
||||
@@ -0,0 +1,6 @@
|
||||
def replace_right(s, old, new, occurrence):
|
||||
# replace_right('1232425', '2', ' ', 1) -> '12324 5'
|
||||
# replace_right('1232425', '2', ' ', 2) -> '123 4 5'
|
||||
|
||||
split = s.rsplit(old, occurrence)
|
||||
return new.join(split)
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
import sympy as sp
|
||||
import numpy as np
|
||||
|
||||
def cross(x):
|
||||
ret = sp.Matrix(np.zeros((3,3)))
|
||||
ret[0,1], ret[0,2] = -x[2], x[1]
|
||||
ret[1,0], ret[1,2] = x[2], -x[0]
|
||||
ret[2,0], ret[2,1] = -x[1], x[0]
|
||||
return ret
|
||||
|
||||
def euler_rotate(roll, pitch, yaw):
|
||||
# make symbolic rotation matrix from eulers
|
||||
matrix_roll = sp.Matrix([[1, 0, 0],
|
||||
[0, sp.cos(roll), -sp.sin(roll)],
|
||||
[0, sp.sin(roll), sp.cos(roll)]])
|
||||
matrix_pitch = sp.Matrix([[sp.cos(pitch), 0, sp.sin(pitch)],
|
||||
[0, 1, 0],
|
||||
[-sp.sin(pitch), 0, sp.cos(pitch)]])
|
||||
matrix_yaw = sp.Matrix([[sp.cos(yaw), -sp.sin(yaw), 0],
|
||||
[sp.sin(yaw), sp.cos(yaw), 0],
|
||||
[0, 0, 1]])
|
||||
return matrix_yaw*matrix_pitch*matrix_roll
|
||||
|
||||
def quat_rotate(q0, q1, q2, q3):
|
||||
# make symbolic rotation matrix from quat
|
||||
return sp.Matrix([[q0**2 + q1**2 - q2**2 - q3**2, 2*(q1*q2 + q0*q3), 2*(q1*q3 - q0*q2)],
|
||||
[2*(q1*q2 - q0*q3), q0**2 - q1**2 + q2**2 - q3**2, 2*(q2*q3 + q0*q1)],
|
||||
[2*(q1*q3 + q0*q2), 2*(q2*q3 - q0*q1), q0**2 - q1**2 - q2**2 + q3**2]]).T
|
||||
|
||||
def quat_matrix_l(p):
|
||||
return sp.Matrix([[p[0], -p[1], -p[2], -p[3]],
|
||||
[p[1], p[0], -p[3], p[2]],
|
||||
[p[2], p[3], p[0], -p[1]],
|
||||
[p[3], -p[2], p[1], p[0]]])
|
||||
|
||||
def quat_matrix_r(p):
|
||||
return sp.Matrix([[p[0], -p[1], -p[2], -p[3]],
|
||||
[p[1], p[0], p[3], -p[2]],
|
||||
[p[2], -p[3], p[0], p[1]],
|
||||
[p[3], p[2], -p[1], p[0]]])
|
||||
|
||||
|
||||
def sympy_into_c(sympy_functions):
|
||||
from sympy.utilities import codegen
|
||||
routines = []
|
||||
for name, expr, args in sympy_functions:
|
||||
r = codegen.make_routine(name, expr, language="C99")
|
||||
|
||||
# argument ordering input to sympy is broken with function with output arguments
|
||||
nargs = []
|
||||
# reorder the input arguments
|
||||
for aa in args:
|
||||
if aa is None:
|
||||
nargs.append(codegen.InputArgument(sp.Symbol('unused'), dimensions=[1,1]))
|
||||
continue
|
||||
found = False
|
||||
for a in r.arguments:
|
||||
if str(aa.name) == str(a.name):
|
||||
nargs.append(a)
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
# [1,1] is a hack for Matrices
|
||||
nargs.append(codegen.InputArgument(aa, dimensions=[1,1]))
|
||||
# add the output arguments
|
||||
for a in r.arguments:
|
||||
if type(a) == codegen.OutputArgument:
|
||||
nargs.append(a)
|
||||
|
||||
#assert len(r.arguments) == len(args)+1
|
||||
r.arguments = nargs
|
||||
|
||||
# add routine to list
|
||||
routines.append(r)
|
||||
|
||||
[(c_name, c_code), (h_name, c_header)] = codegen.get_code_generator('C', 'ekf', 'C99').write(routines, "ekf")
|
||||
c_code = '\n'.join(x for x in c_code.split("\n") if len(x) > 0 and x[0] != '#')
|
||||
c_header = '\n'.join(x for x in c_header.split("\n") if len(x) > 0 and x[0] != '#')
|
||||
|
||||
return c_header, c_code
|
||||
@@ -0,0 +1,28 @@
|
||||
import os
|
||||
import unittest
|
||||
from uuid import uuid4
|
||||
|
||||
from common.file_helpers import atomic_write_on_fs_tmp
|
||||
from common.file_helpers import atomic_write_in_dir
|
||||
|
||||
|
||||
class TestFileHelpers(unittest.TestCase):
|
||||
def run_atomic_write_func(self, atomic_write_func):
|
||||
path = "/tmp/tmp{}".format(uuid4())
|
||||
with atomic_write_func(path) as f:
|
||||
f.write("test")
|
||||
|
||||
with open(path) as f:
|
||||
self.assertEqual(f.read(), "test")
|
||||
self.assertEqual(os.stat(path).st_mode & 0o777, 0o644)
|
||||
os.remove(path)
|
||||
|
||||
def test_atomic_write_on_fs_tmp(self):
|
||||
self.run_atomic_write_func(atomic_write_on_fs_tmp)
|
||||
|
||||
def test_atomic_write_in_dir(self):
|
||||
self.run_atomic_write_func(atomic_write_in_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,27 @@
|
||||
import numpy as np
|
||||
import unittest
|
||||
import timeit
|
||||
|
||||
from common.numpy_fast import interp
|
||||
|
||||
|
||||
class InterpTest(unittest.TestCase):
|
||||
def test_correctness_controls(self):
|
||||
_A_CRUISE_MIN_BP = np.asarray([0., 5., 10., 20., 40.])
|
||||
_A_CRUISE_MIN_V = np.asarray([-1.0, -.8, -.67, -.5, -.30])
|
||||
v_ego_arr = [-1, -1e-12, 0, 4, 5, 6, 7, 10, 11, 15.2, 20, 21, 39,
|
||||
39.999999, 40, 41]
|
||||
|
||||
expected = np.interp(v_ego_arr, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V)
|
||||
actual = interp(v_ego_arr, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V)
|
||||
|
||||
np.testing.assert_equal(actual, expected)
|
||||
|
||||
for v_ego in v_ego_arr:
|
||||
expected = np.interp(v_ego, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V)
|
||||
actual = interp(v_ego, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V)
|
||||
np.testing.assert_equal(actual, expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,63 @@
|
||||
from common.params import Params, UnknownKeyName
|
||||
import threading
|
||||
import time
|
||||
import tempfile
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
|
||||
class TestParams(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmpdir = tempfile.mkdtemp()
|
||||
print("using", self.tmpdir)
|
||||
self.params = Params(self.tmpdir)
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmpdir)
|
||||
|
||||
def test_params_put_and_get(self):
|
||||
self.params.put("DongleId", "cb38263377b873ee")
|
||||
assert self.params.get("DongleId") == b"cb38263377b873ee"
|
||||
|
||||
def test_params_non_ascii(self):
|
||||
st = b"\xe1\x90\xff"
|
||||
self.params.put("CarParams", st)
|
||||
assert self.params.get("CarParams") == st
|
||||
|
||||
def test_params_get_cleared_panda_disconnect(self):
|
||||
self.params.put("CarParams", "test")
|
||||
self.params.put("DongleId", "cb38263377b873ee")
|
||||
assert self.params.get("CarParams") == b"test"
|
||||
self.params.panda_disconnect()
|
||||
assert self.params.get("CarParams") is None
|
||||
assert self.params.get("DongleId") is not None
|
||||
|
||||
def test_params_get_cleared_manager_start(self):
|
||||
self.params.put("CarParams", "test")
|
||||
self.params.put("DongleId", "cb38263377b873ee")
|
||||
assert self.params.get("CarParams") == b"test"
|
||||
self.params.manager_start()
|
||||
assert self.params.get("CarParams") is None
|
||||
assert self.params.get("DongleId") is not None
|
||||
|
||||
def test_params_two_things(self):
|
||||
self.params.put("DongleId", "bob")
|
||||
self.params.put("AccessToken", "knope")
|
||||
assert self.params.get("DongleId") == b"bob"
|
||||
assert self.params.get("AccessToken") == b"knope"
|
||||
|
||||
def test_params_get_block(self):
|
||||
def _delayed_writer():
|
||||
time.sleep(0.1)
|
||||
self.params.put("CarParams", "test")
|
||||
threading.Thread(target=_delayed_writer).start()
|
||||
assert self.params.get("CarParams") is None
|
||||
assert self.params.get("CarParams", True) == b"test"
|
||||
|
||||
def test_params_unknown_key_fails(self):
|
||||
with self.assertRaises(UnknownKeyName):
|
||||
self.params.get("swag")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
Reference Frames
|
||||
------
|
||||
Many reference frames are used throughout. This
|
||||
folder contains all helper functions needed to
|
||||
transform between them. Generally this is done
|
||||
by generating a rotation matrix and multiplying.
|
||||
|
||||
|
||||
| Name | [x, y, z] | Units | Notes |
|
||||
| :-------------: |:-------------:| :-----:| :----: |
|
||||
| Geodetic | [Latitude, Longitude, Altitude] | geodetic coordinates | Sometimes used as [lon, lat, alt], avoid this frame. |
|
||||
| ECEF | [x, y, z] | meters | We use **ITRF14 (IGS14)**, NOT NAD83. <br> This is the global Mesh3D frame. |
|
||||
| NED | [North, East, Down] | meters | Relative to earth's surface, useful for vizualizing. |
|
||||
| Device | [Forward, Right, Down] | meters | This is the Mesh3D local frame. <br> Relative to camera, **not imu.** <br> |
|
||||
| Road | [Forward, Left, Up] | meters | On the road plane aligned to the vehicle. <br>  |
|
||||
| View | [Right, Down, Forward] | meters | Like device frame, but according to camera conventions. |
|
||||
| Camera | [u, v, focal] | pixels | Like view frame, but 2d on the camera image.|
|
||||
| Normalized Camera | [u / focal, v / focal, 1] | / | |
|
||||
| Model | [u, v, focal] | pixels | The sampled rectangle of the full camera frame the model uses. |
|
||||
| Normalized Model | [u / focal, v / focal, 1] | / | |
|
||||
|
||||
|
||||
|
||||
|
||||
Orientation Conventations
|
||||
------
|
||||
Quaternions, rotation matrices and euler angles are three
|
||||
equivalent representations of orientation and all three are
|
||||
used throughout the code base.
|
||||
|
||||
For euler angles the preferred convention is [roll, pitch, yaw]
|
||||
which corresponds to rotations around the [x, y, z] axes. All
|
||||
euler angles should always be in radians or radians/s unless
|
||||
for plotting or display purposes. For quaternions the hamilton
|
||||
notations is preferred which is [q<sub>w</sub>, q<sub>x</sub>, q<sub>y</sub>, q<sub>z</sub>]. All quaternions
|
||||
should always be normalized with a strictly positive q<sub>w</sub>. **These
|
||||
quaternions are a unique representation of orientation whereas euler angles
|
||||
or rotation matrices are not.**
|
||||
|
||||
To rotate from one frame into another with euler angles the
|
||||
convention is to rotate around roll, then pitch and then yaw,
|
||||
while rotating around the rotated axes, not the original axes.
|
||||
|
||||
|
||||
Calibration
|
||||
------
|
||||
EONs are not all mounted in the exact same way. To compensate for the effects of this the vision model takes in an image that is "calibrated". This means the image is aligned so the direction of travel of the car when it is going straight and the road is flat is always in the location on the image. This calibration is defined by a pitch and yaw angle that describe the direction of travel vector in device frame.
|
||||
|
||||
Example
|
||||
------
|
||||
To transform global Mesh3D positions and orientations (positions_ecef, quats_ecef) into the local frame described by the
|
||||
first position and orientation from Mesh3D one would do:
|
||||
```
|
||||
ecef_from_local = rot_from_quat(quats_ecef[0])
|
||||
local_from_ecef = ecef_from_local.T
|
||||
positions_local = np.einsum('ij,kj->ki', local_from_ecef, postions_ecef - positions_ecef[0])
|
||||
rotations_global = rot_from_quat(quats_ecef)
|
||||
rotations_local = np.einsum('ij,kjl->kil', local_from_ecef, rotations_global)
|
||||
eulers_local = euler_from_rot(rotations_local)
|
||||
```
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import numpy as np
|
||||
import unittest
|
||||
|
||||
import common.transformations.coordinates as coord
|
||||
|
||||
geodetic_positions = np.array([[37.7610403, -122.4778699, 115],
|
||||
[27.4840915, -68.5867592, 2380],
|
||||
[32.4916858, -113.652821, -6],
|
||||
[15.1392514, 103.6976037, 24],
|
||||
[24.2302229, 44.2835412, 1650]])
|
||||
|
||||
geodetic_positions_radians = np.array([[0.65905448, -2.13764209, 115],
|
||||
[0.47968789, -1.19706477, 2380],
|
||||
[0.5670869, -1.98361593, -6],
|
||||
[0.26422978, 1.80986461, 24],
|
||||
[0.42289717, 0.7728936, 1650]])
|
||||
|
||||
ecef_positions = np.array([[-2711076.55270557, -4259167.14692758, 3884579.87669935],
|
||||
[ 2068042.69652729, -5273435.40316622, 2927004.89190746],
|
||||
[-2160412.60461669, -4932588.89873832, 3406542.29652851],
|
||||
[-1458247.92550567, 5983060.87496612, 1654984.6099885 ],
|
||||
[ 4167239.10867871, 4064301.90363223, 2602234.6065749 ]])
|
||||
|
||||
ecef_positions_offset = np.array([[-2711004.46961115, -4259099.33540613, 3884605.16002147],
|
||||
[ 2068074.30639499, -5273413.78835412, 2927012.48741131],
|
||||
[-2160344.53748176, -4932586.20092211, 3406636.2962545 ],
|
||||
[-1458211.98517094, 5983151.11161276, 1655077.02698447],
|
||||
[ 4167271.20055269, 4064398.22619263, 2602238.95265847]])
|
||||
|
||||
|
||||
ned_offsets = np.array([[78.722153649976391, 24.396208657446344, 60.343017506838436],
|
||||
[10.699003365155221, 37.319278617604269, 4.1084100025050407],
|
||||
[95.282646251726959, 61.266689955574428, -25.376506058505054],
|
||||
[68.535769283630003, -56.285970011848889, -100.54840137956515],
|
||||
[-33.066609321880179, 46.549821994306861, -84.062540548335591]])
|
||||
|
||||
ecef_init_batch = np.array([2068042.69652729, -5273435.40316622, 2927004.89190746])
|
||||
ecef_positions_offset_batch = np.array([[ 2068089.41454771, -5273434.46829148, 2927074.04783672],
|
||||
[ 2068103.31628647, -5273393.92275431, 2927102.08725987],
|
||||
[ 2068108.49939636, -5273359.27047121, 2927045.07091581],
|
||||
[ 2068075.12395611, -5273381.69432566, 2927041.08207992],
|
||||
[ 2068060.72033399, -5273430.6061505, 2927094.54928305]])
|
||||
|
||||
ned_offsets_batch = np.array([[ 53.88103168, 43.83445935, -46.27488057],
|
||||
[ 93.83378995, 71.57943024, -30.23113187],
|
||||
[ 57.26725796, 89.05602684, 23.02265814],
|
||||
[ 49.71775195, 49.79767572, 17.15351015],
|
||||
[ 78.56272609, 18.53100158, -43.25290759]])
|
||||
|
||||
|
||||
|
||||
class TestNED(unittest.TestCase):
|
||||
def test_small_distances(self):
|
||||
start_geodetic = np.array([33.8042184, -117.888593, 0.0])
|
||||
local_coord = coord.LocalCoord.from_geodetic(start_geodetic)
|
||||
|
||||
start_ned = local_coord.geodetic2ned(start_geodetic)
|
||||
np.testing.assert_array_equal(start_ned, np.zeros(3,))
|
||||
|
||||
west_geodetic = start_geodetic + [0, -0.0005, 0]
|
||||
west_ned = local_coord.geodetic2ned(west_geodetic)
|
||||
self.assertLess(np.abs(west_ned[0]), 1e-3)
|
||||
self.assertLess(west_ned[1], 0)
|
||||
|
||||
southwest_geodetic = start_geodetic + [-0.0005, -0.002, 0]
|
||||
southwest_ned = local_coord.geodetic2ned(southwest_geodetic)
|
||||
self.assertLess(southwest_ned[0], 0)
|
||||
self.assertLess(southwest_ned[1], 0)
|
||||
|
||||
def test_ecef_geodetic(self):
|
||||
# testing single
|
||||
np.testing.assert_allclose(ecef_positions[0], coord.geodetic2ecef(geodetic_positions[0]), rtol=1e-9)
|
||||
np.testing.assert_allclose(geodetic_positions[0,:2], coord.ecef2geodetic(ecef_positions[0])[:2], rtol=1e-9)
|
||||
np.testing.assert_allclose(geodetic_positions[0,2], coord.ecef2geodetic(ecef_positions[0])[2], rtol=1e-9, atol=1e-4)
|
||||
|
||||
np.testing.assert_allclose(geodetic_positions[:,:2], coord.ecef2geodetic(ecef_positions)[:,:2], rtol=1e-9)
|
||||
np.testing.assert_allclose(geodetic_positions[:,2], coord.ecef2geodetic(ecef_positions)[:,2], rtol=1e-9, atol=1e-4)
|
||||
np.testing.assert_allclose(ecef_positions, coord.geodetic2ecef(geodetic_positions), rtol=1e-9)
|
||||
|
||||
np.testing.assert_allclose(geodetic_positions_radians[0], coord.ecef2geodetic(ecef_positions[0], radians=True), rtol=1e-5)
|
||||
np.testing.assert_allclose(geodetic_positions_radians[:,:2], coord.ecef2geodetic(ecef_positions, radians=True)[:,:2], rtol=1e-7)
|
||||
np.testing.assert_allclose(geodetic_positions_radians[:,2], coord.ecef2geodetic(ecef_positions, radians=True)[:,2], rtol=1e-7, atol=1e-4)
|
||||
|
||||
|
||||
|
||||
def test_ned(self):
|
||||
for ecef_pos in ecef_positions:
|
||||
converter = coord.LocalCoord.from_ecef(ecef_pos)
|
||||
ecef_pos_moved = ecef_pos + [25, -25, 25]
|
||||
ecef_pos_moved_double_converted = converter.ned2ecef(converter.ecef2ned(ecef_pos_moved))
|
||||
np.testing.assert_allclose(ecef_pos_moved, ecef_pos_moved_double_converted, rtol=1e-9)
|
||||
|
||||
for geo_pos in geodetic_positions:
|
||||
converter = coord.LocalCoord.from_geodetic(geo_pos)
|
||||
geo_pos_moved = geo_pos + np.array([0, 0, 10])
|
||||
geo_pos_double_converted_moved = converter.ned2geodetic(converter.geodetic2ned(geo_pos) + np.array([0,0,-10]))
|
||||
np.testing.assert_allclose(geo_pos_moved[:2], geo_pos_double_converted_moved[:2], rtol=1e-9, atol=1e-6)
|
||||
np.testing.assert_allclose(geo_pos_moved[2], geo_pos_double_converted_moved[2], rtol=1e-9, atol=1e-4)
|
||||
|
||||
|
||||
def test_ned_saved_results(self):
|
||||
for i, ecef_pos in enumerate(ecef_positions):
|
||||
converter = coord.LocalCoord.from_ecef(ecef_pos)
|
||||
np.testing.assert_allclose(converter.ned2ecef(ned_offsets[i]),
|
||||
ecef_positions_offset[i],
|
||||
rtol=1e-9, atol=1e-4)
|
||||
np.testing.assert_allclose(converter.ecef2ned(ecef_positions_offset[i]),
|
||||
ned_offsets[i],
|
||||
rtol=1e-9, atol=1e-4)
|
||||
|
||||
def test_ned_batch(self):
|
||||
converter = coord.LocalCoord.from_ecef(ecef_init_batch)
|
||||
np.testing.assert_allclose(converter.ecef2ned(ecef_positions_offset_batch),
|
||||
ned_offsets_batch,
|
||||
rtol=1e-9, atol=1e-7)
|
||||
np.testing.assert_allclose(converter.ned2ecef(ned_offsets_batch),
|
||||
ecef_positions_offset_batch,
|
||||
rtol=1e-9, atol=1e-7)
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import numpy as np
|
||||
import unittest
|
||||
|
||||
from common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \
|
||||
rot2quat, quat2rot, \
|
||||
ned_euler_from_ecef
|
||||
|
||||
eulers = np.array([[ 1.46520501, 2.78688383, 2.92780854],
|
||||
[ 4.86909526, 3.60618161, 4.30648981],
|
||||
[ 3.72175965, 2.68763705, 5.43895988],
|
||||
[ 5.92306687, 5.69573614, 0.81100357],
|
||||
[ 0.67838374, 5.02402037, 2.47106426]])
|
||||
|
||||
quats = np.array([[ 0.66855182, -0.71500939, 0.19539353, 0.06017818],
|
||||
[ 0.43163717, 0.70013301, 0.28209145, 0.49389021],
|
||||
[ 0.44121991, -0.08252646, 0.34257534, 0.82532207],
|
||||
[ 0.88578382, -0.04515356, -0.32936046, 0.32383617],
|
||||
[ 0.06578165, 0.61282835, 0.07126891, 0.78424163]])
|
||||
|
||||
ecef_positions = np.array([[-2711076.55270557, -4259167.14692758, 3884579.87669935],
|
||||
[ 2068042.69652729, -5273435.40316622, 2927004.89190746],
|
||||
[-2160412.60461669, -4932588.89873832, 3406542.29652851],
|
||||
[-1458247.92550567, 5983060.87496612, 1654984.6099885 ],
|
||||
[ 4167239.10867871, 4064301.90363223, 2602234.6065749 ]])
|
||||
|
||||
ned_eulers = np.array([[ 0.46806039, -0.4881889 , 1.65697808],
|
||||
[-2.14525969, -0.36533066, 0.73813479],
|
||||
[-1.39523364, -0.58540761, -1.77376356],
|
||||
[-1.84220435, 0.61828016, -1.03310421],
|
||||
[ 2.50450101, 0.36304151, 0.33136365]])
|
||||
|
||||
|
||||
class TestOrientation(unittest.TestCase):
|
||||
def test_quat_euler(self):
|
||||
for i, eul in enumerate(eulers):
|
||||
np.testing.assert_allclose(quats[i], euler2quat(eul), rtol=1e-7)
|
||||
np.testing.assert_allclose(quats[i], euler2quat(quat2euler(quats[i])), rtol=1e-6)
|
||||
for i, eul in enumerate(eulers):
|
||||
np.testing.assert_allclose(quats[i], euler2quat(list(eul)), rtol=1e-7)
|
||||
np.testing.assert_allclose(quats[i], euler2quat(quat2euler(list(quats[i]))), rtol=1e-6)
|
||||
np.testing.assert_allclose(quats, euler2quat(eulers), rtol=1e-7)
|
||||
np.testing.assert_allclose(quats, euler2quat(quat2euler(quats)), rtol=1e-6)
|
||||
|
||||
def test_rot_euler(self):
|
||||
for eul in eulers:
|
||||
np.testing.assert_allclose(euler2quat(eul), euler2quat(rot2euler(euler2rot(eul))), rtol=1e-7)
|
||||
for eul in eulers:
|
||||
np.testing.assert_allclose(euler2quat(eul), euler2quat(rot2euler(euler2rot(list(eul)))), rtol=1e-7)
|
||||
np.testing.assert_allclose(euler2quat(eulers), euler2quat(rot2euler(euler2rot(eulers))), rtol=1e-7)
|
||||
|
||||
def test_rot_quat(self):
|
||||
for quat in quats:
|
||||
np.testing.assert_allclose(quat, rot2quat(quat2rot(quat)), rtol=1e-7)
|
||||
for quat in quats:
|
||||
np.testing.assert_allclose(quat, rot2quat(quat2rot(list(quat))), rtol=1e-7)
|
||||
np.testing.assert_allclose(quats, rot2quat(quat2rot(quats)), rtol=1e-7)
|
||||
|
||||
def test_euler_ned(self):
|
||||
for i in range(len(eulers)):
|
||||
np.testing.assert_allclose(ned_eulers[i], ned_euler_from_ecef(ecef_positions[i], eulers[i]), rtol=1e-7)
|
||||
#np.testing.assert_allclose(eulers[i], ecef_euler_from_ned(ecef_positions[i], ned_eulers[i]), rtol=1e-7)
|
||||
np.testing.assert_allclose(ned_eulers, ned_euler_from_ecef(ecef_positions, eulers), rtol=1e-7)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,40 @@
|
||||
import sys
|
||||
import pygame
|
||||
from pygame.locals import *
|
||||
|
||||
class Window():
|
||||
def __init__(self, w, h, caption="window", double=False):
|
||||
self.w = w
|
||||
self.h = h
|
||||
pygame.init()
|
||||
pygame.display.set_caption(caption)
|
||||
self.double = double
|
||||
if self.double:
|
||||
self.screen = pygame.display.set_mode((w*2,h*2), pygame.DOUBLEBUF)
|
||||
else:
|
||||
self.screen = pygame.display.set_mode((w,h), pygame.DOUBLEBUF)
|
||||
self.camera_surface = pygame.surface.Surface((w,h), 0, 24).convert()
|
||||
|
||||
def draw(self, out):
|
||||
pygame.surfarray.blit_array(self.camera_surface, out.swapaxes(0,1))
|
||||
if self.double:
|
||||
camera_surface_2x = pygame.transform.scale2x(self.camera_surface)
|
||||
self.screen.blit(camera_surface_2x, (0, 0))
|
||||
else:
|
||||
self.screen.blit(self.camera_surface, (0, 0))
|
||||
pygame.display.flip()
|
||||
|
||||
def getkey(self):
|
||||
while 1:
|
||||
event = pygame.event.wait()
|
||||
if event.type == QUIT:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
if event.type == KEYDOWN:
|
||||
return event.key
|
||||
|
||||
def getclick(self):
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.MOUSEBUTTONDOWN:
|
||||
mx, my = pygame.mouse.get_pos()
|
||||
return mx, my
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (C) 2007 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This script is a wrapper for smali.jar, so you can simply call "smali",
|
||||
# instead of java -jar smali.jar. It is heavily based on the "dx" script
|
||||
# from the Android SDK
|
||||
|
||||
# Set up prog to be the path of this script, including following symlinks,
|
||||
# and set up progdir to be the fully-qualified pathname of its directory.
|
||||
prog="$0"
|
||||
while [ -h "${prog}" ]; do
|
||||
newProg=`/bin/ls -ld "${prog}"`
|
||||
|
||||
newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
|
||||
if expr "x${newProg}" : 'x/' >/dev/null; then
|
||||
prog="${newProg}"
|
||||
else
|
||||
progdir=`dirname "${prog}"`
|
||||
prog="${progdir}/${newProg}"
|
||||
fi
|
||||
done
|
||||
oldwd=`pwd`
|
||||
progdir=`dirname "${prog}"`
|
||||
cd "${progdir}"
|
||||
progdir=`pwd`
|
||||
prog="${progdir}"/`basename "${prog}"`
|
||||
cd "${oldwd}"
|
||||
|
||||
jarfile=apktool.jar
|
||||
libdir="$progdir"
|
||||
if [ ! -r "$libdir/$jarfile" ]
|
||||
then
|
||||
echo `basename "$prog"`": can't find $jarfile"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
javaOpts=""
|
||||
|
||||
# If you want DX to have more memory when executing, uncomment the following
|
||||
# line and adjust the value accordingly. Use "java -X" for a list of options
|
||||
# you can pass here.
|
||||
#
|
||||
javaOpts="-Xmx512M -Dfile.encoding=utf-8"
|
||||
|
||||
# Alternatively, this will extract any parameter "-Jxxx" from the command line
|
||||
# and pass them to Java (instead of to dx). This makes it possible for you to
|
||||
# add a command-line parameter such as "-JXmx256M" in your ant scripts, for
|
||||
# example.
|
||||
while expr "x$1" : 'x-J' >/dev/null; do
|
||||
opt=`expr "$1" : '-J\(.*\)'`
|
||||
javaOpts="${javaOpts} -${opt}"
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "$OSTYPE" = "cygwin" ] ; then
|
||||
jarpath=`cygpath -w "$libdir/$jarfile"`
|
||||
else
|
||||
jarpath="$libdir/$jarfile"
|
||||
fi
|
||||
|
||||
# add current location to path for aapt
|
||||
PATH=$PATH:`pwd`;
|
||||
export PATH;
|
||||
exec java $javaOpts -jar "$jarpath" "$@"
|
||||
+1
@@ -0,0 +1 @@
|
||||
apktool_2.3.0.jar
|
||||
Vendored
BIN
Binary file not shown.
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b7d08710ae4c0bbdc73184453b4d40da979fa3b0abbdb4e974980f9250730127
|
||||
size 18914367
|
||||
+1
@@ -0,0 +1 @@
|
||||
../gpac/bin/MP4Box
|
||||
+1
@@ -0,0 +1 @@
|
||||
../apktool/apktool
|
||||
+1
@@ -0,0 +1 @@
|
||||
../azcopy/azcopy
|
||||
+1
@@ -0,0 +1 @@
|
||||
../capnp/bin/capnp
|
||||
+1
@@ -0,0 +1 @@
|
||||
../capnp/bin/capnpc
|
||||
+1
@@ -0,0 +1 @@
|
||||
../capnp/bin/capnpc-c
|
||||
+1
@@ -0,0 +1 @@
|
||||
../capnp/bin/capnpc-c++
|
||||
+1
@@ -0,0 +1 @@
|
||||
../capnp/bin/capnpc-capnp
|
||||
+1
@@ -0,0 +1 @@
|
||||
../capnp/bin/capnpc-java
|
||||
+1
@@ -0,0 +1 @@
|
||||
../ffmpeg/bin/ffmpeg
|
||||
+1
@@ -0,0 +1 @@
|
||||
../ffmpeg/bin/ffprobe
|
||||
+1
@@ -0,0 +1 @@
|
||||
../pyflame/flame.sh
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user