wip
This commit is contained in:
1
cereal/.dockerignore
Normal file
1
cereal/.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.sconsign.dblite
|
||||||
28
cereal/.github/workflows/repo.yml
vendored
Normal file
28
cereal/.github/workflows/repo.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
name: repo
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 15 * * 2"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pre-commit-autoupdate:
|
||||||
|
name: pre-commit autoupdate
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
container:
|
||||||
|
image: ghcr.io/commaai/cereal:latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: pre-commit autoupdate
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
pre-commit autoupdate
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.ACTIONS_CREATE_PR_PAT }}
|
||||||
|
commit-message: Update pre-commit hook versions
|
||||||
|
title: 'pre-commit: autoupdate hooks'
|
||||||
|
branch: pre-commit-updates
|
||||||
|
base: master
|
||||||
|
delete-branch: true
|
||||||
61
cereal/.github/workflows/tests.yml
vendored
Normal file
61
cereal/.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: tests
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
env:
|
||||||
|
DOCKER_REGISTRY: ghcr.io/commaai
|
||||||
|
RUN: docker run -e PYTHONWARNINGS=error --shm-size 1G --name cereal cereal /bin/sh -c
|
||||||
|
RUN_NAMED: docker run -e PYTHONWARNINGS=error --shm-size 1G --rm cereal /bin/sh -c
|
||||||
|
CI_RUN: docker run -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID --rm cerealci /bin/bash -c
|
||||||
|
BUILD: docker buildx build --pull --load --cache-to type=inline --cache-from $DOCKER_REGISTRY/cereal:latest -t cereal -f Dockerfile .
|
||||||
|
PYTHONWARNINGS: error
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: build
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build docker image
|
||||||
|
run: eval "$BUILD"
|
||||||
|
- name: Push to dockerhub
|
||||||
|
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/cereal'
|
||||||
|
run: |
|
||||||
|
docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
docker tag cereal $DOCKER_REGISTRY/cereal:latest
|
||||||
|
docker push $DOCKER_REGISTRY/cereal:latest
|
||||||
|
|
||||||
|
unit_tests:
|
||||||
|
name: unit tests
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
flags: ['', '--asan', '--ubsan']
|
||||||
|
backend: ['MSGQ', 'ZMQ']
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build docker image
|
||||||
|
run: eval "$BUILD"
|
||||||
|
- name: C++ tests
|
||||||
|
run: |
|
||||||
|
$RUN "export ${{ matrix.backend }}=1 && \
|
||||||
|
scons ${{ matrix.flags }} -j$(nproc) && \
|
||||||
|
messaging/test_runner && \
|
||||||
|
visionipc/test_runner"
|
||||||
|
- name: python tests
|
||||||
|
run: $RUN_NAMED "${{ matrix.backend }}=1 coverage run -m unittest discover ."
|
||||||
|
- name: Upload coverage
|
||||||
|
run: |
|
||||||
|
docker commit cereal cerealci
|
||||||
|
$CI_RUN "cd /project/cereal && bash <(curl -s https://codecov.io/bash) -v -F unit_tests_${{ matrix.backend }}"
|
||||||
|
|
||||||
|
static_analysis:
|
||||||
|
name: static analysis
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build docker image
|
||||||
|
run: eval "$BUILD"
|
||||||
|
- name: Static analysis
|
||||||
|
# TODO: a package pre-commit installs has a warning, remove the unset once that's fixed
|
||||||
|
run: $RUN "git init && git add -A && unset PYTHONWARNINGS && pre-commit run --all"
|
||||||
47
cereal/.pre-commit-config.yaml
Normal file
47
cereal/.pre-commit-config.yaml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.5.0
|
||||||
|
hooks:
|
||||||
|
- id: check-ast
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-executables-have-shebangs
|
||||||
|
- id: check-shebang-scripts-are-executable
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
|
rev: v1.9.0
|
||||||
|
hooks:
|
||||||
|
- id: mypy
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.3.2
|
||||||
|
hooks:
|
||||||
|
- id: ruff
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: cppcheck
|
||||||
|
name: cppcheck
|
||||||
|
entry: cppcheck
|
||||||
|
language: system
|
||||||
|
types: [c++]
|
||||||
|
exclude: '^(messaging/msgq_tests.cc|messaging/test_runner.cc)'
|
||||||
|
args:
|
||||||
|
- --error-exitcode=1
|
||||||
|
- --inline-suppr
|
||||||
|
- --language=c++
|
||||||
|
- --force
|
||||||
|
- --quiet
|
||||||
|
- -j4
|
||||||
|
- repo: https://github.com/cpplint/cpplint
|
||||||
|
rev: 1.6.1
|
||||||
|
hooks:
|
||||||
|
- id: cpplint
|
||||||
|
args:
|
||||||
|
- --quiet
|
||||||
|
- --counting=detailed
|
||||||
|
- --linelength=240
|
||||||
|
- --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces
|
||||||
|
- repo: https://github.com/codespell-project/codespell
|
||||||
|
rev: v2.2.6
|
||||||
|
hooks:
|
||||||
|
- id: codespell
|
||||||
|
args:
|
||||||
|
- -L ned
|
||||||
|
- --builtins clear,rare,informal,usage,code,names,en-GB_to_en-US
|
||||||
58
cereal/Dockerfile
Normal file
58
cereal/Dockerfile
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
autoconf \
|
||||||
|
build-essential \
|
||||||
|
ca-certificates \
|
||||||
|
capnproto \
|
||||||
|
clang \
|
||||||
|
cppcheck \
|
||||||
|
curl \
|
||||||
|
git \
|
||||||
|
libbz2-dev \
|
||||||
|
libcapnp-dev \
|
||||||
|
libffi-dev \
|
||||||
|
liblzma-dev \
|
||||||
|
libncurses5-dev \
|
||||||
|
libncursesw5-dev \
|
||||||
|
libreadline-dev \
|
||||||
|
libsqlite3-dev \
|
||||||
|
libssl-dev \
|
||||||
|
libtool \
|
||||||
|
libzmq3-dev \
|
||||||
|
llvm \
|
||||||
|
make \
|
||||||
|
cmake \
|
||||||
|
ocl-icd-opencl-dev \
|
||||||
|
opencl-headers \
|
||||||
|
python-openssl \
|
||||||
|
tk-dev \
|
||||||
|
wget \
|
||||||
|
xz-utils \
|
||||||
|
zlib1g-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
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.11.4 && \
|
||||||
|
pyenv global 3.11.4 && \
|
||||||
|
pyenv rehash && \
|
||||||
|
pip3 install --no-cache-dir pyyaml Cython scons pycapnp pre-commit ruff parameterized coverage numpy
|
||||||
|
|
||||||
|
WORKDIR /project/
|
||||||
|
RUN cd /tmp/ && \
|
||||||
|
git clone https://github.com/catchorg/Catch2.git && \
|
||||||
|
cd Catch2 && \
|
||||||
|
git checkout 229cc4823c8cbe67366da8179efc6089dd3893e9 && \
|
||||||
|
mv single_include/catch2/ /project/ && \
|
||||||
|
cd .. \
|
||||||
|
rm -rf Catch2
|
||||||
|
|
||||||
|
WORKDIR /project/cereal
|
||||||
|
|
||||||
|
ENV PYTHONPATH=/project
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN rm -rf .git && \
|
||||||
|
scons -c && scons -j$(nproc)
|
||||||
7
cereal/LICENSE
Normal file
7
cereal/LICENSE
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Copyright (c) 2020, Comma.ai, Inc.
|
||||||
|
|
||||||
|
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.
|
||||||
60
cereal/README.md
Normal file
60
cereal/README.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# What is cereal? [](https://github.com/commaai/cereal/actions) [](https://codecov.io/gh/commaai/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.
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
- **All fields must describe quantities in SI units**, unless otherwise specified in the field name.
|
||||||
|
- In the context of the message they are in, field names should be completely unambiguous.
|
||||||
|
- All values should be easy to plot and be human-readable with minimal parsing.
|
||||||
|
|
||||||
|
### Maintaining backwards-compatibility
|
||||||
|
|
||||||
|
When making changes to the messaging spec you want to maintain backwards-compatibility, such that old logs can
|
||||||
|
be parsed with a new version of cereal. Adding structs and adding members to structs is generally safe, most other
|
||||||
|
things are not. Read more details [here](https://capnproto.org/language.html).
|
||||||
|
|
||||||
|
### Custom forks
|
||||||
|
|
||||||
|
Forks of [openpilot](https://github.com/commaai/openpilot) might want to add things to the messaging
|
||||||
|
spec, however this could conflict with future changes made in mainline cereal/openpilot. Rebasing against mainline openpilot
|
||||||
|
then means breaking backwards-compatibility with all old logs of your fork. So we added reserved events in
|
||||||
|
[custom.capnp](custom.capnp) that we will leave empty in mainline cereal/openpilot. **If you only modify those, you can ensure your
|
||||||
|
fork will remain backwards-compatible with all versions of mainline cereal/openpilot and your fork.**
|
||||||
|
|
||||||
|
## Pub Sub Backends
|
||||||
|
|
||||||
|
cereal supports two backends, one based on [zmq](https://zeromq.org/) and another called [msgq](messaging/msgq.cc), 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'])
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# in publisher
|
||||||
|
pm = messaging.PubMaster(['sensorEvents'])
|
||||||
|
dat = messaging.new_message('sensorEvents', size=1)
|
||||||
|
dat.sensorEvents[0] = {"gyro": {"v": [0.1, -0.1, 0.1]}}
|
||||||
|
pm.send('sensorEvents', dat)
|
||||||
|
```
|
||||||
78
cereal/SConscript
Normal file
78
cereal/SConscript
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
Import('env', 'envCython', 'arch', 'common')
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
cereal_dir = Dir('.')
|
||||||
|
gen_dir = Dir('gen')
|
||||||
|
messaging_dir = Dir('messaging')
|
||||||
|
|
||||||
|
# Build cereal
|
||||||
|
|
||||||
|
schema_files = ['log.capnp', 'car.capnp', 'legacy.capnp', 'custom.capnp']
|
||||||
|
env.Command(["gen/c/include/c++.capnp.h"], [], "mkdir -p " + gen_dir.path + "/c/include && touch $TARGETS")
|
||||||
|
env.Command([f'gen/cpp/{s}.c++' for s in schema_files] + [f'gen/cpp/{s}.h' for s in schema_files],
|
||||||
|
schema_files,
|
||||||
|
f"capnpc --src-prefix={cereal_dir.path} $SOURCES -o c++:{gen_dir.path}/cpp/")
|
||||||
|
|
||||||
|
# TODO: remove non shared cereal and messaging
|
||||||
|
cereal_objects = env.SharedObject([f'gen/cpp/{s}.c++' for s in schema_files])
|
||||||
|
|
||||||
|
cereal = env.Library('cereal', cereal_objects)
|
||||||
|
env.SharedLibrary('cereal_shared', cereal_objects)
|
||||||
|
|
||||||
|
# Build messaging
|
||||||
|
|
||||||
|
services_h = env.Command(['services.h'], ['services.py'], 'python3 ' + cereal_dir.path + '/services.py > $TARGET')
|
||||||
|
|
||||||
|
messaging_objects = env.SharedObject([
|
||||||
|
'messaging/messaging.cc',
|
||||||
|
'messaging/event.cc',
|
||||||
|
'messaging/impl_zmq.cc',
|
||||||
|
'messaging/impl_msgq.cc',
|
||||||
|
'messaging/impl_fake.cc',
|
||||||
|
'messaging/msgq.cc',
|
||||||
|
'messaging/socketmaster.cc',
|
||||||
|
])
|
||||||
|
|
||||||
|
messaging = env.Library('messaging', messaging_objects)
|
||||||
|
Depends('messaging/impl_zmq.cc', services_h)
|
||||||
|
|
||||||
|
env.Program('messaging/bridge', ['messaging/bridge.cc'], LIBS=[messaging, 'zmq', common])
|
||||||
|
Depends('messaging/bridge.cc', services_h)
|
||||||
|
|
||||||
|
messaging_python = envCython.Program('messaging/messaging_pyx.so', 'messaging/messaging_pyx.pyx', LIBS=envCython["LIBS"]+[messaging, "zmq", common])
|
||||||
|
|
||||||
|
# Build Vision IPC
|
||||||
|
vipc_sources = [
|
||||||
|
'visionipc/ipc.cc',
|
||||||
|
'visionipc/visionipc_server.cc',
|
||||||
|
'visionipc/visionipc_client.cc',
|
||||||
|
'visionipc/visionbuf.cc',
|
||||||
|
]
|
||||||
|
|
||||||
|
if arch == "larch64":
|
||||||
|
vipc_sources += ['visionipc/visionbuf_ion.cc']
|
||||||
|
else:
|
||||||
|
vipc_sources += ['visionipc/visionbuf_cl.cc']
|
||||||
|
|
||||||
|
vipc_objects = env.SharedObject(vipc_sources)
|
||||||
|
visionipc = env.Library('visionipc', vipc_objects)
|
||||||
|
|
||||||
|
|
||||||
|
vipc_frameworks = []
|
||||||
|
vipc_libs = envCython["LIBS"] + [visionipc, messaging, common, "zmq"]
|
||||||
|
if arch == "Darwin":
|
||||||
|
vipc_frameworks.append('OpenCL')
|
||||||
|
else:
|
||||||
|
vipc_libs.append('OpenCL')
|
||||||
|
envCython.Program('visionipc/visionipc_pyx.so', 'visionipc/visionipc_pyx.pyx',
|
||||||
|
LIBS=vipc_libs, FRAMEWORKS=vipc_frameworks)
|
||||||
|
|
||||||
|
if GetOption('extras'):
|
||||||
|
env.Program('messaging/test_runner', ['messaging/test_runner.cc', 'messaging/msgq_tests.cc'], LIBS=[messaging, common])
|
||||||
|
|
||||||
|
env.Program('visionipc/test_runner', ['visionipc/test_runner.cc', 'visionipc/visionipc_tests.cc'],
|
||||||
|
LIBS=['pthread'] + vipc_libs, FRAMEWORKS=vipc_frameworks)
|
||||||
|
|
||||||
|
|
||||||
|
Export('cereal', 'messaging', 'messaging_python', 'visionipc')
|
||||||
88
cereal/SConstruct
Normal file
88
cereal/SConstruct
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
import sysconfig
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
arch = "Darwin"
|
||||||
|
|
||||||
|
common = ''
|
||||||
|
|
||||||
|
cpppath = [
|
||||||
|
f"#/../",
|
||||||
|
'/usr/lib/include',
|
||||||
|
'/opt/homebrew/include',
|
||||||
|
sysconfig.get_paths()['include'],
|
||||||
|
]
|
||||||
|
|
||||||
|
libpath = [
|
||||||
|
'/opt/homebrew/lib',
|
||||||
|
]
|
||||||
|
|
||||||
|
AddOption('--minimal',
|
||||||
|
action='store_false',
|
||||||
|
dest='extras',
|
||||||
|
default=True,
|
||||||
|
help='the minimum build. no tests, tools, etc.')
|
||||||
|
|
||||||
|
AddOption('--asan',
|
||||||
|
action='store_true',
|
||||||
|
help='turn on ASAN')
|
||||||
|
|
||||||
|
AddOption('--ubsan',
|
||||||
|
action='store_true',
|
||||||
|
help='turn on UBSan')
|
||||||
|
|
||||||
|
ccflags = []
|
||||||
|
ldflags = []
|
||||||
|
if GetOption('ubsan'):
|
||||||
|
flags = [
|
||||||
|
"-fsanitize=undefined",
|
||||||
|
"-fno-sanitize-recover=undefined",
|
||||||
|
]
|
||||||
|
ccflags += flags
|
||||||
|
ldflags += flags
|
||||||
|
elif GetOption('asan'):
|
||||||
|
ccflags += ["-fsanitize=address", "-fno-omit-frame-pointer"]
|
||||||
|
ldflags += ["-fsanitize=address"]
|
||||||
|
|
||||||
|
env = Environment(
|
||||||
|
ENV=os.environ,
|
||||||
|
CC='clang',
|
||||||
|
CXX='clang++',
|
||||||
|
CCFLAGS=[
|
||||||
|
"-g",
|
||||||
|
"-fPIC",
|
||||||
|
"-O2",
|
||||||
|
"-Wunused",
|
||||||
|
"-Werror",
|
||||||
|
"-Wshadow",
|
||||||
|
] + ccflags,
|
||||||
|
LDFLAGS=ldflags,
|
||||||
|
LINKFLAGS=ldflags,
|
||||||
|
|
||||||
|
CFLAGS="-std=gnu11",
|
||||||
|
CXXFLAGS="-std=c++1z",
|
||||||
|
CPPPATH=cpppath,
|
||||||
|
LIBPATH=libpath,
|
||||||
|
CYTHONCFILESUFFIX=".cpp",
|
||||||
|
tools=["default", "cython"]
|
||||||
|
)
|
||||||
|
|
||||||
|
Export('env', 'arch', 'common')
|
||||||
|
|
||||||
|
envCython = env.Clone(LIBS=[])
|
||||||
|
envCython["CPPPATH"] += [np.get_include()]
|
||||||
|
envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-shadow", "-Wno-deprecated-declarations"]
|
||||||
|
envCython["CCFLAGS"].remove('-Werror')
|
||||||
|
if arch == "Darwin":
|
||||||
|
envCython["LINKFLAGS"] = ["-bundle", "-undefined", "dynamic_lookup"]
|
||||||
|
else:
|
||||||
|
envCython["LINKFLAGS"] = ["-pthread", "-shared"]
|
||||||
|
|
||||||
|
Export('envCython')
|
||||||
|
|
||||||
|
|
||||||
|
SConscript(['SConscript'])
|
||||||
@@ -120,22 +120,24 @@ struct CarEvent @0x9b1657f34caf3ad3 {
|
|||||||
# FrogPilot events
|
# FrogPilot events
|
||||||
accel30 @120;
|
accel30 @120;
|
||||||
accel35 @121;
|
accel35 @121;
|
||||||
firefoxSteerSaturated @122;
|
accel40 @122;
|
||||||
frogSteerSaturated @123;
|
blockUser @123;
|
||||||
greenLight @124;
|
firefoxSteerSaturated @124;
|
||||||
holidayActive @125;
|
goatSteerSaturated @125;
|
||||||
laneChangeBlockedLoud @126;
|
greenLight @126;
|
||||||
leadDeparting @127;
|
holidayActive @127;
|
||||||
noLaneAvailable @128;
|
laneChangeBlockedLoud @128;
|
||||||
openpilotCrashed @129;
|
leadDeparting @129;
|
||||||
openpilotCrashedRandomEvents @130;
|
noLaneAvailable @130;
|
||||||
pedalInterceptorNoBrake @131;
|
openpilotCrashed @131;
|
||||||
speedLimitChanged @132;
|
openpilotCrashedRandomEvents @132;
|
||||||
torqueNNLoad @133;
|
pedalInterceptorNoBrake @133;
|
||||||
turningLeft @134;
|
speedLimitChanged @134;
|
||||||
turningRight @135;
|
torqueNNLoad @135;
|
||||||
vCruise69 @136;
|
turningLeft @136;
|
||||||
yourFrogTriedToKillMe @137;
|
turningRight @137;
|
||||||
|
vCruise69 @138;
|
||||||
|
yourFrogTriedToKillMe @139;
|
||||||
|
|
||||||
radarCanErrorDEPRECATED @15;
|
radarCanErrorDEPRECATED @15;
|
||||||
communityFeatureDisallowedDEPRECATED @62;
|
communityFeatureDisallowedDEPRECATED @62;
|
||||||
@@ -343,14 +345,12 @@ struct CarControl {
|
|||||||
# Actuator commands as computed by controlsd
|
# Actuator commands as computed by controlsd
|
||||||
actuators @6 :Actuators;
|
actuators @6 :Actuators;
|
||||||
|
|
||||||
|
# moved to CarOutput
|
||||||
|
actuatorsOutputDEPRECATED @10 :Actuators;
|
||||||
|
|
||||||
leftBlinker @15: Bool;
|
leftBlinker @15: Bool;
|
||||||
rightBlinker @16: Bool;
|
rightBlinker @16: Bool;
|
||||||
|
|
||||||
# Any car specific rate limits or quirks applied by
|
|
||||||
# the CarController are reflected in actuatorsOutput
|
|
||||||
# and matches what is sent to the car
|
|
||||||
actuatorsOutput @10 :Actuators;
|
|
||||||
|
|
||||||
orientationNED @13 :List(Float32);
|
orientationNED @13 :List(Float32);
|
||||||
angularVelocity @14 :List(Float32);
|
angularVelocity @14 :List(Float32);
|
||||||
|
|
||||||
@@ -400,7 +400,7 @@ struct CarControl {
|
|||||||
leftLaneVisible @7: Bool;
|
leftLaneVisible @7: Bool;
|
||||||
rightLaneDepart @8: Bool;
|
rightLaneDepart @8: Bool;
|
||||||
leftLaneDepart @9: Bool;
|
leftLaneDepart @9: Bool;
|
||||||
leadVelocity @10: Float32;
|
leadDistanceBars @10: Int8; # 1-3: 1 is closest, 3 is farthest. some ports may utilize 2-4 bars instead
|
||||||
|
|
||||||
enum VisualAlert {
|
enum VisualAlert {
|
||||||
# these are the choices from the Honda
|
# these are the choices from the Honda
|
||||||
@@ -431,14 +431,15 @@ struct CarControl {
|
|||||||
|
|
||||||
# Random Events
|
# Random Events
|
||||||
angry @9;
|
angry @9;
|
||||||
fart @10;
|
doc @10;
|
||||||
firefox @11;
|
fart @11;
|
||||||
nessie @12;
|
firefox @12;
|
||||||
noice @13;
|
nessie @13;
|
||||||
uwu @14;
|
noice @14;
|
||||||
|
uwu @15;
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
goat @15;
|
goat @16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,6 +451,13 @@ struct CarControl {
|
|||||||
pitchDEPRECATED @9 :Float32;
|
pitchDEPRECATED @9 :Float32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CarOutput {
|
||||||
|
# Any car specific rate limits or quirks applied by
|
||||||
|
# the CarController are reflected in actuatorsOutput
|
||||||
|
# and matches what is sent to the car
|
||||||
|
actuatorsOutput @0 :CarControl.Actuators;
|
||||||
|
}
|
||||||
|
|
||||||
# ****** car param ******
|
# ****** car param ******
|
||||||
|
|
||||||
struct CarParams {
|
struct CarParams {
|
||||||
@@ -629,6 +637,7 @@ struct CarParams {
|
|||||||
hyundaiCanfd @28;
|
hyundaiCanfd @28;
|
||||||
volkswagenMqbEvo @29;
|
volkswagenMqbEvo @29;
|
||||||
chryslerCusw @30;
|
chryslerCusw @30;
|
||||||
|
psa @31;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SteerControlType {
|
enum SteerControlType {
|
||||||
|
|||||||
8
cereal/codecov.yml
Normal file
8
cereal/codecov.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
comment: false
|
||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
informational: true
|
||||||
|
patch: off
|
||||||
|
|
||||||
@@ -11,37 +11,52 @@ $Cxx.namespace("cereal");
|
|||||||
struct FrogPilotCarControl @0x81c2f05a394cf4af {
|
struct FrogPilotCarControl @0x81c2f05a394cf4af {
|
||||||
alwaysOnLateral @0 :Bool;
|
alwaysOnLateral @0 :Bool;
|
||||||
speedLimitChanged @1 :Bool;
|
speedLimitChanged @1 :Bool;
|
||||||
|
trafficModeActive @2 :Bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FrogPilotDeviceState @0xaedffd8f31e7b55d {
|
struct FrogPilotCarState @0xaedffd8f31e7b55d {
|
||||||
|
struct ButtonEvent {
|
||||||
|
enum Type {
|
||||||
|
lkas @0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FrogPilotDeviceState @0xf35cc4560bbf6ec2 {
|
||||||
freeSpace @0 :Int16;
|
freeSpace @0 :Int16;
|
||||||
usedSpace @1 :Int16;
|
usedSpace @1 :Int16;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FrogPilotNavigation @0xf35cc4560bbf6ec2 {
|
struct FrogPilotNavigation @0xda96579883444c35 {
|
||||||
approachingIntersection @0 :Bool;
|
approachingIntersection @0 :Bool;
|
||||||
approachingTurn @1 :Bool;
|
approachingTurn @1 :Bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FrogPilotPlan @0xda96579883444c35 {
|
struct FrogPilotPlan @0x80ae746ee2596b11 {
|
||||||
adjustedCruise @0 :Float64;
|
accelerationJerk @0 :Float32;
|
||||||
conditionalExperimental @1 :Bool;
|
accelerationJerkStock @1 :Float32;
|
||||||
desiredFollowDistance @2 :Int16;
|
adjustedCruise @2 :Float64;
|
||||||
laneWidthLeft @3 :Float32;
|
conditionalExperimental @3 :Bool;
|
||||||
laneWidthRight @4 :Float32;
|
desiredFollowDistance @4 :Int16;
|
||||||
redLight @5 :Bool;
|
egoJerk @5 :Float32;
|
||||||
safeObstacleDistance @6 :Int16;
|
egoJerkStock @6 :Float32;
|
||||||
safeObstacleDistanceStock @7 :Int16;
|
jerk @7 :Float32;
|
||||||
slcOverridden @8 :Bool;
|
laneWidthLeft @8 :Float32;
|
||||||
slcOverriddenSpeed @9 :Float64;
|
laneWidthRight @9 :Float32;
|
||||||
slcSpeedLimit @10 :Float64;
|
minAcceleration @10 :Float32;
|
||||||
slcSpeedLimitOffset @11 :Float32;
|
maxAcceleration @11 :Float32;
|
||||||
stoppedEquivalenceFactor @12 :Int16;
|
redLight @12 :Bool;
|
||||||
unconfirmedSlcSpeedLimit @13 :Float64;
|
safeObstacleDistance @13 :Int16;
|
||||||
vtscControllingCurve @14 :Bool;
|
safeObstacleDistanceStock @14 :Int16;
|
||||||
}
|
slcOverridden @15 :Bool;
|
||||||
|
slcOverriddenSpeed @16 :Float64;
|
||||||
struct CustomReserved4 @0x80ae746ee2596b11 {
|
slcSpeedLimit @17 :Float64;
|
||||||
|
slcSpeedLimitOffset @18 :Float32;
|
||||||
|
stoppedEquivalenceFactor @19 :Int16;
|
||||||
|
tFollow @20 :Float32;
|
||||||
|
unconfirmedSlcSpeedLimit @21 :Float64;
|
||||||
|
vCruise @22 :Float32;
|
||||||
|
vtscControllingCurve @23 :Bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CustomReserved5 @0xa5cd762cd951a455 {
|
struct CustomReserved5 @0xa5cd762cd951a455 {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,762 +0,0 @@
|
|||||||
// Generated by Cap'n Proto compiler, DO NOT EDIT
|
|
||||||
// source: custom.capnp
|
|
||||||
|
|
||||||
#include "custom.capnp.h"
|
|
||||||
|
|
||||||
namespace capnp {
|
|
||||||
namespace schemas {
|
|
||||||
static const ::capnp::_::AlignedData<52> b_81c2f05a394cf4af = {
|
|
||||||
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
|
||||||
175, 244, 76, 57, 90, 240, 194, 129,
|
|
||||||
13, 0, 0, 0, 1, 0, 1, 0,
|
|
||||||
89, 10, 85, 29, 102, 186, 38, 181,
|
|
||||||
0, 0, 7, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
21, 0, 0, 0, 10, 1, 0, 0,
|
|
||||||
37, 0, 0, 0, 7, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
33, 0, 0, 0, 119, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
99, 117, 115, 116, 111, 109, 46, 99,
|
|
||||||
97, 112, 110, 112, 58, 70, 114, 111,
|
|
||||||
103, 80, 105, 108, 111, 116, 67, 97,
|
|
||||||
114, 67, 111, 110, 116, 114, 111, 108,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 1, 0, 1, 0,
|
|
||||||
8, 0, 0, 0, 3, 0, 4, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
41, 0, 0, 0, 130, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
40, 0, 0, 0, 3, 0, 1, 0,
|
|
||||||
52, 0, 0, 0, 2, 0, 1, 0,
|
|
||||||
1, 0, 0, 0, 1, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 1, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
49, 0, 0, 0, 146, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
52, 0, 0, 0, 3, 0, 1, 0,
|
|
||||||
64, 0, 0, 0, 2, 0, 1, 0,
|
|
||||||
97, 108, 119, 97, 121, 115, 79, 110,
|
|
||||||
76, 97, 116, 101, 114, 97, 108, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
115, 112, 101, 101, 100, 76, 105, 109,
|
|
||||||
105, 116, 67, 104, 97, 110, 103, 101,
|
|
||||||
100, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, }
|
|
||||||
};
|
|
||||||
::capnp::word const* const bp_81c2f05a394cf4af = b_81c2f05a394cf4af.words;
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
static const uint16_t m_81c2f05a394cf4af[] = {0, 1};
|
|
||||||
static const uint16_t i_81c2f05a394cf4af[] = {0, 1};
|
|
||||||
const ::capnp::_::RawSchema s_81c2f05a394cf4af = {
|
|
||||||
0x81c2f05a394cf4af, b_81c2f05a394cf4af.words, 52, nullptr, m_81c2f05a394cf4af,
|
|
||||||
0, 2, i_81c2f05a394cf4af, nullptr, nullptr, { &s_81c2f05a394cf4af, nullptr, nullptr, 0, 0, nullptr }, false
|
|
||||||
};
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
static const ::capnp::_::AlignedData<51> b_aedffd8f31e7b55d = {
|
|
||||||
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
|
||||||
93, 181, 231, 49, 143, 253, 223, 174,
|
|
||||||
13, 0, 0, 0, 1, 0, 1, 0,
|
|
||||||
89, 10, 85, 29, 102, 186, 38, 181,
|
|
||||||
0, 0, 7, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
21, 0, 0, 0, 18, 1, 0, 0,
|
|
||||||
37, 0, 0, 0, 7, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
33, 0, 0, 0, 119, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
99, 117, 115, 116, 111, 109, 46, 99,
|
|
||||||
97, 112, 110, 112, 58, 70, 114, 111,
|
|
||||||
103, 80, 105, 108, 111, 116, 68, 101,
|
|
||||||
118, 105, 99, 101, 83, 116, 97, 116,
|
|
||||||
101, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 1, 0, 1, 0,
|
|
||||||
8, 0, 0, 0, 3, 0, 4, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
41, 0, 0, 0, 82, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
40, 0, 0, 0, 3, 0, 1, 0,
|
|
||||||
52, 0, 0, 0, 2, 0, 1, 0,
|
|
||||||
1, 0, 0, 0, 1, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 1, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
49, 0, 0, 0, 82, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
48, 0, 0, 0, 3, 0, 1, 0,
|
|
||||||
60, 0, 0, 0, 2, 0, 1, 0,
|
|
||||||
102, 114, 101, 101, 83, 112, 97, 99,
|
|
||||||
101, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
117, 115, 101, 100, 83, 112, 97, 99,
|
|
||||||
101, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, }
|
|
||||||
};
|
|
||||||
::capnp::word const* const bp_aedffd8f31e7b55d = b_aedffd8f31e7b55d.words;
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
static const uint16_t m_aedffd8f31e7b55d[] = {0, 1};
|
|
||||||
static const uint16_t i_aedffd8f31e7b55d[] = {0, 1};
|
|
||||||
const ::capnp::_::RawSchema s_aedffd8f31e7b55d = {
|
|
||||||
0xaedffd8f31e7b55d, b_aedffd8f31e7b55d.words, 51, nullptr, m_aedffd8f31e7b55d,
|
|
||||||
0, 2, i_aedffd8f31e7b55d, nullptr, nullptr, { &s_aedffd8f31e7b55d, nullptr, nullptr, 0, 0, nullptr }, false
|
|
||||||
};
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
static const ::capnp::_::AlignedData<52> b_f35cc4560bbf6ec2 = {
|
|
||||||
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
|
||||||
194, 110, 191, 11, 86, 196, 92, 243,
|
|
||||||
13, 0, 0, 0, 1, 0, 1, 0,
|
|
||||||
89, 10, 85, 29, 102, 186, 38, 181,
|
|
||||||
0, 0, 7, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
21, 0, 0, 0, 10, 1, 0, 0,
|
|
||||||
37, 0, 0, 0, 7, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
33, 0, 0, 0, 119, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
99, 117, 115, 116, 111, 109, 46, 99,
|
|
||||||
97, 112, 110, 112, 58, 70, 114, 111,
|
|
||||||
103, 80, 105, 108, 111, 116, 78, 97,
|
|
||||||
118, 105, 103, 97, 116, 105, 111, 110,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 1, 0, 1, 0,
|
|
||||||
8, 0, 0, 0, 3, 0, 4, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
41, 0, 0, 0, 194, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
44, 0, 0, 0, 3, 0, 1, 0,
|
|
||||||
56, 0, 0, 0, 2, 0, 1, 0,
|
|
||||||
1, 0, 0, 0, 1, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 1, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
53, 0, 0, 0, 130, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
52, 0, 0, 0, 3, 0, 1, 0,
|
|
||||||
64, 0, 0, 0, 2, 0, 1, 0,
|
|
||||||
97, 112, 112, 114, 111, 97, 99, 104,
|
|
||||||
105, 110, 103, 73, 110, 116, 101, 114,
|
|
||||||
115, 101, 99, 116, 105, 111, 110, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
97, 112, 112, 114, 111, 97, 99, 104,
|
|
||||||
105, 110, 103, 84, 117, 114, 110, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, }
|
|
||||||
};
|
|
||||||
::capnp::word const* const bp_f35cc4560bbf6ec2 = b_f35cc4560bbf6ec2.words;
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
static const uint16_t m_f35cc4560bbf6ec2[] = {0, 1};
|
|
||||||
static const uint16_t i_f35cc4560bbf6ec2[] = {0, 1};
|
|
||||||
const ::capnp::_::RawSchema s_f35cc4560bbf6ec2 = {
|
|
||||||
0xf35cc4560bbf6ec2, b_f35cc4560bbf6ec2.words, 52, nullptr, m_f35cc4560bbf6ec2,
|
|
||||||
0, 2, i_f35cc4560bbf6ec2, nullptr, nullptr, { &s_f35cc4560bbf6ec2, nullptr, nullptr, 0, 0, nullptr }, false
|
|
||||||
};
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
static const ::capnp::_::AlignedData<270> b_da96579883444c35 = {
|
|
||||||
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
|
||||||
53, 76, 68, 131, 152, 87, 150, 218,
|
|
||||||
13, 0, 0, 0, 1, 0, 7, 0,
|
|
||||||
89, 10, 85, 29, 102, 186, 38, 181,
|
|
||||||
0, 0, 7, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
21, 0, 0, 0, 218, 0, 0, 0,
|
|
||||||
33, 0, 0, 0, 7, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
29, 0, 0, 0, 79, 3, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
99, 117, 115, 116, 111, 109, 46, 99,
|
|
||||||
97, 112, 110, 112, 58, 70, 114, 111,
|
|
||||||
103, 80, 105, 108, 111, 116, 80, 108,
|
|
||||||
97, 110, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 1, 0, 1, 0,
|
|
||||||
60, 0, 0, 0, 3, 0, 4, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
149, 1, 0, 0, 122, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
148, 1, 0, 0, 3, 0, 1, 0,
|
|
||||||
160, 1, 0, 0, 2, 0, 1, 0,
|
|
||||||
1, 0, 0, 0, 64, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 1, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
157, 1, 0, 0, 194, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
160, 1, 0, 0, 3, 0, 1, 0,
|
|
||||||
172, 1, 0, 0, 2, 0, 1, 0,
|
|
||||||
2, 0, 0, 0, 5, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 2, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
169, 1, 0, 0, 178, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
172, 1, 0, 0, 3, 0, 1, 0,
|
|
||||||
184, 1, 0, 0, 2, 0, 1, 0,
|
|
||||||
3, 0, 0, 0, 3, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 3, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
181, 1, 0, 0, 114, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
180, 1, 0, 0, 3, 0, 1, 0,
|
|
||||||
192, 1, 0, 0, 2, 0, 1, 0,
|
|
||||||
4, 0, 0, 0, 4, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 4, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
189, 1, 0, 0, 122, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
188, 1, 0, 0, 3, 0, 1, 0,
|
|
||||||
200, 1, 0, 0, 2, 0, 1, 0,
|
|
||||||
5, 0, 0, 0, 65, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 5, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
197, 1, 0, 0, 74, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
196, 1, 0, 0, 3, 0, 1, 0,
|
|
||||||
208, 1, 0, 0, 2, 0, 1, 0,
|
|
||||||
6, 0, 0, 0, 10, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 6, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
205, 1, 0, 0, 170, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
208, 1, 0, 0, 3, 0, 1, 0,
|
|
||||||
220, 1, 0, 0, 2, 0, 1, 0,
|
|
||||||
7, 0, 0, 0, 11, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 7, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
217, 1, 0, 0, 210, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
224, 1, 0, 0, 3, 0, 1, 0,
|
|
||||||
236, 1, 0, 0, 2, 0, 1, 0,
|
|
||||||
8, 0, 0, 0, 66, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 8, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
233, 1, 0, 0, 114, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
232, 1, 0, 0, 3, 0, 1, 0,
|
|
||||||
244, 1, 0, 0, 2, 0, 1, 0,
|
|
||||||
9, 0, 0, 0, 3, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 9, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
241, 1, 0, 0, 154, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
244, 1, 0, 0, 3, 0, 1, 0,
|
|
||||||
0, 2, 0, 0, 2, 0, 1, 0,
|
|
||||||
10, 0, 0, 0, 4, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 10, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
253, 1, 0, 0, 114, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
252, 1, 0, 0, 3, 0, 1, 0,
|
|
||||||
8, 2, 0, 0, 2, 0, 1, 0,
|
|
||||||
11, 0, 0, 0, 10, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 11, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
5, 2, 0, 0, 162, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
8, 2, 0, 0, 3, 0, 1, 0,
|
|
||||||
20, 2, 0, 0, 2, 0, 1, 0,
|
|
||||||
12, 0, 0, 0, 22, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 12, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
17, 2, 0, 0, 202, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
24, 2, 0, 0, 3, 0, 1, 0,
|
|
||||||
36, 2, 0, 0, 2, 0, 1, 0,
|
|
||||||
13, 0, 0, 0, 6, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 13, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
33, 2, 0, 0, 202, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
40, 2, 0, 0, 3, 0, 1, 0,
|
|
||||||
52, 2, 0, 0, 2, 0, 1, 0,
|
|
||||||
14, 0, 0, 0, 67, 0, 0, 0,
|
|
||||||
0, 0, 1, 0, 14, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
49, 2, 0, 0, 170, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
52, 2, 0, 0, 3, 0, 1, 0,
|
|
||||||
64, 2, 0, 0, 2, 0, 1, 0,
|
|
||||||
97, 100, 106, 117, 115, 116, 101, 100,
|
|
||||||
67, 114, 117, 105, 115, 101, 0, 0,
|
|
||||||
11, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
11, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
99, 111, 110, 100, 105, 116, 105, 111,
|
|
||||||
110, 97, 108, 69, 120, 112, 101, 114,
|
|
||||||
105, 109, 101, 110, 116, 97, 108, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
100, 101, 115, 105, 114, 101, 100, 70,
|
|
||||||
111, 108, 108, 111, 119, 68, 105, 115,
|
|
||||||
116, 97, 110, 99, 101, 0, 0, 0,
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
108, 97, 110, 101, 87, 105, 100, 116,
|
|
||||||
104, 76, 101, 102, 116, 0, 0, 0,
|
|
||||||
10, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
10, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
108, 97, 110, 101, 87, 105, 100, 116,
|
|
||||||
104, 82, 105, 103, 104, 116, 0, 0,
|
|
||||||
10, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
10, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
114, 101, 100, 76, 105, 103, 104, 116,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
115, 97, 102, 101, 79, 98, 115, 116,
|
|
||||||
97, 99, 108, 101, 68, 105, 115, 116,
|
|
||||||
97, 110, 99, 101, 0, 0, 0, 0,
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
115, 97, 102, 101, 79, 98, 115, 116,
|
|
||||||
97, 99, 108, 101, 68, 105, 115, 116,
|
|
||||||
97, 110, 99, 101, 83, 116, 111, 99,
|
|
||||||
107, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
115, 108, 99, 79, 118, 101, 114, 114,
|
|
||||||
105, 100, 100, 101, 110, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
115, 108, 99, 79, 118, 101, 114, 114,
|
|
||||||
105, 100, 100, 101, 110, 83, 112, 101,
|
|
||||||
101, 100, 0, 0, 0, 0, 0, 0,
|
|
||||||
11, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
11, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
115, 108, 99, 83, 112, 101, 101, 100,
|
|
||||||
76, 105, 109, 105, 116, 0, 0, 0,
|
|
||||||
11, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
11, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
115, 108, 99, 83, 112, 101, 101, 100,
|
|
||||||
76, 105, 109, 105, 116, 79, 102, 102,
|
|
||||||
115, 101, 116, 0, 0, 0, 0, 0,
|
|
||||||
10, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
10, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
115, 116, 111, 112, 112, 101, 100, 69,
|
|
||||||
113, 117, 105, 118, 97, 108, 101, 110,
|
|
||||||
99, 101, 70, 97, 99, 116, 111, 114,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
3, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
117, 110, 99, 111, 110, 102, 105, 114,
|
|
||||||
109, 101, 100, 83, 108, 99, 83, 112,
|
|
||||||
101, 101, 100, 76, 105, 109, 105, 116,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
11, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
11, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
118, 116, 115, 99, 67, 111, 110, 116,
|
|
||||||
114, 111, 108, 108, 105, 110, 103, 67,
|
|
||||||
117, 114, 118, 101, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, }
|
|
||||||
};
|
|
||||||
::capnp::word const* const bp_da96579883444c35 = b_da96579883444c35.words;
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
static const uint16_t m_da96579883444c35[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
|
|
||||||
static const uint16_t i_da96579883444c35[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
|
|
||||||
const ::capnp::_::RawSchema s_da96579883444c35 = {
|
|
||||||
0xda96579883444c35, b_da96579883444c35.words, 270, nullptr, m_da96579883444c35,
|
|
||||||
0, 15, i_da96579883444c35, nullptr, nullptr, { &s_da96579883444c35, nullptr, nullptr, 0, 0, nullptr }, false
|
|
||||||
};
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
static const ::capnp::_::AlignedData<17> b_80ae746ee2596b11 = {
|
|
||||||
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
|
||||||
17, 107, 89, 226, 110, 116, 174, 128,
|
|
||||||
13, 0, 0, 0, 1, 0, 0, 0,
|
|
||||||
89, 10, 85, 29, 102, 186, 38, 181,
|
|
||||||
0, 0, 7, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
21, 0, 0, 0, 234, 0, 0, 0,
|
|
||||||
33, 0, 0, 0, 7, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
99, 117, 115, 116, 111, 109, 46, 99,
|
|
||||||
97, 112, 110, 112, 58, 67, 117, 115,
|
|
||||||
116, 111, 109, 82, 101, 115, 101, 114,
|
|
||||||
118, 101, 100, 52, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 1, 0, 1, 0, }
|
|
||||||
};
|
|
||||||
::capnp::word const* const bp_80ae746ee2596b11 = b_80ae746ee2596b11.words;
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
const ::capnp::_::RawSchema s_80ae746ee2596b11 = {
|
|
||||||
0x80ae746ee2596b11, b_80ae746ee2596b11.words, 17, nullptr, nullptr,
|
|
||||||
0, 0, nullptr, nullptr, nullptr, { &s_80ae746ee2596b11, nullptr, nullptr, 0, 0, nullptr }, false
|
|
||||||
};
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
static const ::capnp::_::AlignedData<17> b_a5cd762cd951a455 = {
|
|
||||||
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
|
||||||
85, 164, 81, 217, 44, 118, 205, 165,
|
|
||||||
13, 0, 0, 0, 1, 0, 0, 0,
|
|
||||||
89, 10, 85, 29, 102, 186, 38, 181,
|
|
||||||
0, 0, 7, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
21, 0, 0, 0, 234, 0, 0, 0,
|
|
||||||
33, 0, 0, 0, 7, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
99, 117, 115, 116, 111, 109, 46, 99,
|
|
||||||
97, 112, 110, 112, 58, 67, 117, 115,
|
|
||||||
116, 111, 109, 82, 101, 115, 101, 114,
|
|
||||||
118, 101, 100, 53, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 1, 0, 1, 0, }
|
|
||||||
};
|
|
||||||
::capnp::word const* const bp_a5cd762cd951a455 = b_a5cd762cd951a455.words;
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
const ::capnp::_::RawSchema s_a5cd762cd951a455 = {
|
|
||||||
0xa5cd762cd951a455, b_a5cd762cd951a455.words, 17, nullptr, nullptr,
|
|
||||||
0, 0, nullptr, nullptr, nullptr, { &s_a5cd762cd951a455, nullptr, nullptr, 0, 0, nullptr }, false
|
|
||||||
};
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
static const ::capnp::_::AlignedData<17> b_f98d843bfd7004a3 = {
|
|
||||||
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
|
||||||
163, 4, 112, 253, 59, 132, 141, 249,
|
|
||||||
13, 0, 0, 0, 1, 0, 0, 0,
|
|
||||||
89, 10, 85, 29, 102, 186, 38, 181,
|
|
||||||
0, 0, 7, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
21, 0, 0, 0, 234, 0, 0, 0,
|
|
||||||
33, 0, 0, 0, 7, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
99, 117, 115, 116, 111, 109, 46, 99,
|
|
||||||
97, 112, 110, 112, 58, 67, 117, 115,
|
|
||||||
116, 111, 109, 82, 101, 115, 101, 114,
|
|
||||||
118, 101, 100, 54, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 1, 0, 1, 0, }
|
|
||||||
};
|
|
||||||
::capnp::word const* const bp_f98d843bfd7004a3 = b_f98d843bfd7004a3.words;
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
const ::capnp::_::RawSchema s_f98d843bfd7004a3 = {
|
|
||||||
0xf98d843bfd7004a3, b_f98d843bfd7004a3.words, 17, nullptr, nullptr,
|
|
||||||
0, 0, nullptr, nullptr, nullptr, { &s_f98d843bfd7004a3, nullptr, nullptr, 0, 0, nullptr }, false
|
|
||||||
};
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
static const ::capnp::_::AlignedData<17> b_b86e6369214c01c8 = {
|
|
||||||
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
|
||||||
200, 1, 76, 33, 105, 99, 110, 184,
|
|
||||||
13, 0, 0, 0, 1, 0, 0, 0,
|
|
||||||
89, 10, 85, 29, 102, 186, 38, 181,
|
|
||||||
0, 0, 7, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
21, 0, 0, 0, 234, 0, 0, 0,
|
|
||||||
33, 0, 0, 0, 7, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
99, 117, 115, 116, 111, 109, 46, 99,
|
|
||||||
97, 112, 110, 112, 58, 67, 117, 115,
|
|
||||||
116, 111, 109, 82, 101, 115, 101, 114,
|
|
||||||
118, 101, 100, 55, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 1, 0, 1, 0, }
|
|
||||||
};
|
|
||||||
::capnp::word const* const bp_b86e6369214c01c8 = b_b86e6369214c01c8.words;
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
const ::capnp::_::RawSchema s_b86e6369214c01c8 = {
|
|
||||||
0xb86e6369214c01c8, b_b86e6369214c01c8.words, 17, nullptr, nullptr,
|
|
||||||
0, 0, nullptr, nullptr, nullptr, { &s_b86e6369214c01c8, nullptr, nullptr, 0, 0, nullptr }, false
|
|
||||||
};
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
static const ::capnp::_::AlignedData<17> b_f416ec09499d9d19 = {
|
|
||||||
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
|
||||||
25, 157, 157, 73, 9, 236, 22, 244,
|
|
||||||
13, 0, 0, 0, 1, 0, 0, 0,
|
|
||||||
89, 10, 85, 29, 102, 186, 38, 181,
|
|
||||||
0, 0, 7, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
21, 0, 0, 0, 234, 0, 0, 0,
|
|
||||||
33, 0, 0, 0, 7, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
99, 117, 115, 116, 111, 109, 46, 99,
|
|
||||||
97, 112, 110, 112, 58, 67, 117, 115,
|
|
||||||
116, 111, 109, 82, 101, 115, 101, 114,
|
|
||||||
118, 101, 100, 56, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 1, 0, 1, 0, }
|
|
||||||
};
|
|
||||||
::capnp::word const* const bp_f416ec09499d9d19 = b_f416ec09499d9d19.words;
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
const ::capnp::_::RawSchema s_f416ec09499d9d19 = {
|
|
||||||
0xf416ec09499d9d19, b_f416ec09499d9d19.words, 17, nullptr, nullptr,
|
|
||||||
0, 0, nullptr, nullptr, nullptr, { &s_f416ec09499d9d19, nullptr, nullptr, 0, 0, nullptr }, false
|
|
||||||
};
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
static const ::capnp::_::AlignedData<17> b_a1680744031fdb2d = {
|
|
||||||
{ 0, 0, 0, 0, 5, 0, 6, 0,
|
|
||||||
45, 219, 31, 3, 68, 7, 104, 161,
|
|
||||||
13, 0, 0, 0, 1, 0, 0, 0,
|
|
||||||
89, 10, 85, 29, 102, 186, 38, 181,
|
|
||||||
0, 0, 7, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
21, 0, 0, 0, 234, 0, 0, 0,
|
|
||||||
33, 0, 0, 0, 7, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
99, 117, 115, 116, 111, 109, 46, 99,
|
|
||||||
97, 112, 110, 112, 58, 67, 117, 115,
|
|
||||||
116, 111, 109, 82, 101, 115, 101, 114,
|
|
||||||
118, 101, 100, 57, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 1, 0, 1, 0, }
|
|
||||||
};
|
|
||||||
::capnp::word const* const bp_a1680744031fdb2d = b_a1680744031fdb2d.words;
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
const ::capnp::_::RawSchema s_a1680744031fdb2d = {
|
|
||||||
0xa1680744031fdb2d, b_a1680744031fdb2d.words, 17, nullptr, nullptr,
|
|
||||||
0, 0, nullptr, nullptr, nullptr, { &s_a1680744031fdb2d, nullptr, nullptr, 0, 0, nullptr }, false
|
|
||||||
};
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
} // namespace schemas
|
|
||||||
} // namespace capnp
|
|
||||||
|
|
||||||
// =======================================================================================
|
|
||||||
|
|
||||||
namespace cereal {
|
|
||||||
|
|
||||||
// FrogPilotCarControl
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr uint16_t FrogPilotCarControl::_capnpPrivate::dataWordSize;
|
|
||||||
constexpr uint16_t FrogPilotCarControl::_capnpPrivate::pointerCount;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr ::capnp::Kind FrogPilotCarControl::_capnpPrivate::kind;
|
|
||||||
constexpr ::capnp::_::RawSchema const* FrogPilotCarControl::_capnpPrivate::schema;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
|
|
||||||
// FrogPilotDeviceState
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr uint16_t FrogPilotDeviceState::_capnpPrivate::dataWordSize;
|
|
||||||
constexpr uint16_t FrogPilotDeviceState::_capnpPrivate::pointerCount;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr ::capnp::Kind FrogPilotDeviceState::_capnpPrivate::kind;
|
|
||||||
constexpr ::capnp::_::RawSchema const* FrogPilotDeviceState::_capnpPrivate::schema;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
|
|
||||||
// FrogPilotNavigation
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr uint16_t FrogPilotNavigation::_capnpPrivate::dataWordSize;
|
|
||||||
constexpr uint16_t FrogPilotNavigation::_capnpPrivate::pointerCount;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr ::capnp::Kind FrogPilotNavigation::_capnpPrivate::kind;
|
|
||||||
constexpr ::capnp::_::RawSchema const* FrogPilotNavigation::_capnpPrivate::schema;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
|
|
||||||
// FrogPilotPlan
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr uint16_t FrogPilotPlan::_capnpPrivate::dataWordSize;
|
|
||||||
constexpr uint16_t FrogPilotPlan::_capnpPrivate::pointerCount;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr ::capnp::Kind FrogPilotPlan::_capnpPrivate::kind;
|
|
||||||
constexpr ::capnp::_::RawSchema const* FrogPilotPlan::_capnpPrivate::schema;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
|
|
||||||
// CustomReserved4
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr uint16_t CustomReserved4::_capnpPrivate::dataWordSize;
|
|
||||||
constexpr uint16_t CustomReserved4::_capnpPrivate::pointerCount;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr ::capnp::Kind CustomReserved4::_capnpPrivate::kind;
|
|
||||||
constexpr ::capnp::_::RawSchema const* CustomReserved4::_capnpPrivate::schema;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
|
|
||||||
// CustomReserved5
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr uint16_t CustomReserved5::_capnpPrivate::dataWordSize;
|
|
||||||
constexpr uint16_t CustomReserved5::_capnpPrivate::pointerCount;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr ::capnp::Kind CustomReserved5::_capnpPrivate::kind;
|
|
||||||
constexpr ::capnp::_::RawSchema const* CustomReserved5::_capnpPrivate::schema;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
|
|
||||||
// CustomReserved6
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr uint16_t CustomReserved6::_capnpPrivate::dataWordSize;
|
|
||||||
constexpr uint16_t CustomReserved6::_capnpPrivate::pointerCount;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr ::capnp::Kind CustomReserved6::_capnpPrivate::kind;
|
|
||||||
constexpr ::capnp::_::RawSchema const* CustomReserved6::_capnpPrivate::schema;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
|
|
||||||
// CustomReserved7
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr uint16_t CustomReserved7::_capnpPrivate::dataWordSize;
|
|
||||||
constexpr uint16_t CustomReserved7::_capnpPrivate::pointerCount;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr ::capnp::Kind CustomReserved7::_capnpPrivate::kind;
|
|
||||||
constexpr ::capnp::_::RawSchema const* CustomReserved7::_capnpPrivate::schema;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
|
|
||||||
// CustomReserved8
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr uint16_t CustomReserved8::_capnpPrivate::dataWordSize;
|
|
||||||
constexpr uint16_t CustomReserved8::_capnpPrivate::pointerCount;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr ::capnp::Kind CustomReserved8::_capnpPrivate::kind;
|
|
||||||
constexpr ::capnp::_::RawSchema const* CustomReserved8::_capnpPrivate::schema;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
|
|
||||||
// CustomReserved9
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr uint16_t CustomReserved9::_capnpPrivate::dataWordSize;
|
|
||||||
constexpr uint16_t CustomReserved9::_capnpPrivate::pointerCount;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#if !CAPNP_LITE
|
|
||||||
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
constexpr ::capnp::Kind CustomReserved9::_capnpPrivate::kind;
|
|
||||||
constexpr ::capnp::_::RawSchema const* CustomReserved9::_capnpPrivate::schema;
|
|
||||||
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
|
|
||||||
#endif // !CAPNP_LITE
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
26
cereal/generate_javascript.sh
Normal file
26
cereal/generate_javascript.sh
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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
|
||||||
Binary file not shown.
@@ -57,6 +57,7 @@ struct InitData {
|
|||||||
tici @4;
|
tici @4;
|
||||||
pc @5;
|
pc @5;
|
||||||
tizi @6;
|
tizi @6;
|
||||||
|
mici @7;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PandaInfo {
|
struct PandaInfo {
|
||||||
@@ -250,7 +251,7 @@ struct SensorEventData {
|
|||||||
|
|
||||||
# android struct GpsLocation
|
# android struct GpsLocation
|
||||||
struct GpsLocationData {
|
struct GpsLocationData {
|
||||||
# Contains GpsLocationFlags bits.
|
# Contains module-specific flags.
|
||||||
flags @0 :UInt16;
|
flags @0 :UInt16;
|
||||||
|
|
||||||
# Represents latitude in degrees.
|
# Represents latitude in degrees.
|
||||||
@@ -268,8 +269,8 @@ struct GpsLocationData {
|
|||||||
# Represents heading in degrees.
|
# Represents heading in degrees.
|
||||||
bearingDeg @5 :Float32;
|
bearingDeg @5 :Float32;
|
||||||
|
|
||||||
# Represents expected accuracy in meters. (presumably 1 sigma?)
|
# Represents expected horizontal accuracy in meters.
|
||||||
accuracy @6 :Float32;
|
horizontalAccuracy @6 :Float32;
|
||||||
|
|
||||||
unixTimestampMillis @7 :Int64;
|
unixTimestampMillis @7 :Int64;
|
||||||
|
|
||||||
@@ -287,6 +288,8 @@ struct GpsLocationData {
|
|||||||
# Represents velocity accuracy in m/s. (presumably 1 sigma?)
|
# Represents velocity accuracy in m/s. (presumably 1 sigma?)
|
||||||
speedAccuracy @12 :Float32;
|
speedAccuracy @12 :Float32;
|
||||||
|
|
||||||
|
hasFix @13 :Bool;
|
||||||
|
|
||||||
enum SensorSource {
|
enum SensorSource {
|
||||||
android @0;
|
android @0;
|
||||||
iOS @1;
|
iOS @1;
|
||||||
@@ -297,6 +300,7 @@ struct GpsLocationData {
|
|||||||
ublox @6;
|
ublox @6;
|
||||||
trimble @7;
|
trimble @7;
|
||||||
qcomdiag @8;
|
qcomdiag @8;
|
||||||
|
unicore @9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,6 +341,8 @@ struct CanData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct DeviceState @0xa4d8b5af2aa492eb {
|
struct DeviceState @0xa4d8b5af2aa492eb {
|
||||||
|
deviceType @45 :InitData.DeviceType;
|
||||||
|
|
||||||
networkType @22 :NetworkType;
|
networkType @22 :NetworkType;
|
||||||
networkInfo @31 :NetworkInfo;
|
networkInfo @31 :NetworkInfo;
|
||||||
networkStrength @24 :NetworkStrength;
|
networkStrength @24 :NetworkStrength;
|
||||||
@@ -687,6 +693,7 @@ struct ControlsState @0x97ff69c53601abf1 {
|
|||||||
active @36 :Bool;
|
active @36 :Bool;
|
||||||
|
|
||||||
experimentalMode @64 :Bool;
|
experimentalMode @64 :Bool;
|
||||||
|
personality @66 :LongitudinalPersonality;
|
||||||
|
|
||||||
longControlState @30 :Car.CarControl.Actuators.LongControlState;
|
longControlState @30 :Car.CarControl.Actuators.LongControlState;
|
||||||
vPid @2 :Float32;
|
vPid @2 :Float32;
|
||||||
@@ -1056,7 +1063,6 @@ struct LongitudinalPlan @0xe00b5b3eba12876c {
|
|||||||
jerks @34 :List(Float32);
|
jerks @34 :List(Float32);
|
||||||
|
|
||||||
solverExecutionTime @35 :Float32;
|
solverExecutionTime @35 :Float32;
|
||||||
personality @36 :LongitudinalPersonality;
|
|
||||||
|
|
||||||
enum LongitudinalPlanSource {
|
enum LongitudinalPlanSource {
|
||||||
cruise @0;
|
cruise @0;
|
||||||
@@ -1094,6 +1100,7 @@ struct LongitudinalPlan @0xe00b5b3eba12876c {
|
|||||||
eventsDEPRECATED @13 :List(Car.CarEvent);
|
eventsDEPRECATED @13 :List(Car.CarEvent);
|
||||||
gpsTrajectoryDEPRECATED @12 :GpsTrajectory;
|
gpsTrajectoryDEPRECATED @12 :GpsTrajectory;
|
||||||
gpsPlannerActiveDEPRECATED @19 :Bool;
|
gpsPlannerActiveDEPRECATED @19 :Bool;
|
||||||
|
personalityDEPRECATED @36 :LongitudinalPersonality;
|
||||||
|
|
||||||
struct GpsTrajectory {
|
struct GpsTrajectory {
|
||||||
x @0 :List(Float32);
|
x @0 :List(Float32);
|
||||||
@@ -2259,6 +2266,7 @@ struct Event {
|
|||||||
liveCalibration @19 :LiveCalibrationData;
|
liveCalibration @19 :LiveCalibrationData;
|
||||||
carState @22 :Car.CarState;
|
carState @22 :Car.CarState;
|
||||||
carControl @23 :Car.CarControl;
|
carControl @23 :Car.CarControl;
|
||||||
|
carOutput @127 :Car.CarOutput;
|
||||||
longitudinalPlan @24 :LongitudinalPlan;
|
longitudinalPlan @24 :LongitudinalPlan;
|
||||||
uiPlan @106 :UiPlan;
|
uiPlan @106 :UiPlan;
|
||||||
ubloxGnss @34 :UbloxGnss;
|
ubloxGnss @34 :UbloxGnss;
|
||||||
@@ -2332,10 +2340,10 @@ struct Event {
|
|||||||
|
|
||||||
# *********** Custom: reserved for forks ***********
|
# *********** Custom: reserved for forks ***********
|
||||||
frogpilotCarControl @107 :Custom.FrogPilotCarControl;
|
frogpilotCarControl @107 :Custom.FrogPilotCarControl;
|
||||||
frogpilotDeviceState @108 :Custom.FrogPilotDeviceState;
|
frogpilotCarState @108 :Custom.FrogPilotCarState;
|
||||||
frogpilotNavigation @109 :Custom.FrogPilotNavigation;
|
frogpilotDeviceState @109 :Custom.FrogPilotDeviceState;
|
||||||
frogpilotPlan @110 :Custom.FrogPilotPlan;
|
frogpilotNavigation @110 :Custom.FrogPilotNavigation;
|
||||||
customReserved4 @111 :Custom.CustomReserved4;
|
frogpilotPlan @111 :Custom.FrogPilotPlan;
|
||||||
customReserved5 @112 :Custom.CustomReserved5;
|
customReserved5 @112 :Custom.CustomReserved5;
|
||||||
customReserved6 @113 :Custom.CustomReserved6;
|
customReserved6 @113 :Custom.CustomReserved6;
|
||||||
customReserved7 @114 :Custom.CustomReserved7;
|
customReserved7 @114 :Custom.CustomReserved7;
|
||||||
|
|||||||
21
cereal/logger/logger.h
Normal file
21
cereal/logger/logger.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef SWAGLOG
|
||||||
|
// cppcheck-suppress preprocessorErrorDirective
|
||||||
|
#include SWAGLOG
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define CLOUDLOG_DEBUG 10
|
||||||
|
#define CLOUDLOG_INFO 20
|
||||||
|
#define CLOUDLOG_WARNING 30
|
||||||
|
#define CLOUDLOG_ERROR 40
|
||||||
|
#define CLOUDLOG_CRITICAL 50
|
||||||
|
|
||||||
|
#define cloudlog(lvl, fmt, ...) printf(fmt "\n", ## __VA_ARGS__)
|
||||||
|
|
||||||
|
#define LOGD(fmt, ...) cloudlog(CLOUDLOG_DEBUG, fmt, ## __VA_ARGS__)
|
||||||
|
#define LOG(fmt, ...) cloudlog(CLOUDLOG_INFO, fmt, ## __VA_ARGS__)
|
||||||
|
#define LOGW(fmt, ...) cloudlog(CLOUDLOG_WARNING, fmt, ## __VA_ARGS__)
|
||||||
|
#define LOGE(fmt, ...) cloudlog(CLOUDLOG_ERROR, fmt, ## __VA_ARGS__)
|
||||||
|
|
||||||
|
#endif
|
||||||
49
cereal/maptile.capnp
Normal file
49
cereal/maptile.capnp
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Cxx = import "./include/c++.capnp";
|
||||||
|
$Cxx.namespace("cereal");
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
Binary file not shown.
92
cereal/messaging/bridge.cc
Normal file
92
cereal/messaging/bridge.cc
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <csignal>
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
typedef void (*sighandler_t)(int sig);
|
||||||
|
|
||||||
|
#include "cereal/services.h"
|
||||||
|
#include "cereal/messaging/impl_msgq.h"
|
||||||
|
#include "cereal/messaging/impl_zmq.h"
|
||||||
|
|
||||||
|
std::atomic<bool> do_exit = false;
|
||||||
|
static void set_do_exit(int sig) {
|
||||||
|
do_exit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sigpipe_handler(int sig) {
|
||||||
|
assert(sig == SIGPIPE);
|
||||||
|
std::cout << "SIGPIPE received" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string> get_services(std::string whitelist_str, bool zmq_to_msgq) {
|
||||||
|
std::vector<std::string> service_list;
|
||||||
|
for (const auto& it : services) {
|
||||||
|
std::string name = it.second.name;
|
||||||
|
bool in_whitelist = whitelist_str.find(name) != std::string::npos;
|
||||||
|
if (name == "plusFrame" || name == "uiLayoutState" || (zmq_to_msgq && !in_whitelist)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
service_list.push_back(name);
|
||||||
|
}
|
||||||
|
return service_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
signal(SIGPIPE, (sighandler_t)sigpipe_handler);
|
||||||
|
signal(SIGINT, (sighandler_t)set_do_exit);
|
||||||
|
signal(SIGTERM, (sighandler_t)set_do_exit);
|
||||||
|
|
||||||
|
bool zmq_to_msgq = argc > 2;
|
||||||
|
std::string ip = zmq_to_msgq ? argv[1] : "127.0.0.1";
|
||||||
|
std::string whitelist_str = zmq_to_msgq ? std::string(argv[2]) : "";
|
||||||
|
|
||||||
|
Poller *poller;
|
||||||
|
Context *pub_context;
|
||||||
|
Context *sub_context;
|
||||||
|
if (zmq_to_msgq) { // republishes zmq debugging messages as msgq
|
||||||
|
poller = new ZMQPoller();
|
||||||
|
pub_context = new MSGQContext();
|
||||||
|
sub_context = new ZMQContext();
|
||||||
|
} else {
|
||||||
|
poller = new MSGQPoller();
|
||||||
|
pub_context = new ZMQContext();
|
||||||
|
sub_context = new MSGQContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<SubSocket*, PubSocket*> sub2pub;
|
||||||
|
for (auto endpoint : get_services(whitelist_str, zmq_to_msgq)) {
|
||||||
|
PubSocket * pub_sock;
|
||||||
|
SubSocket * sub_sock;
|
||||||
|
if (zmq_to_msgq) {
|
||||||
|
pub_sock = new MSGQPubSocket();
|
||||||
|
sub_sock = new ZMQSubSocket();
|
||||||
|
} else {
|
||||||
|
pub_sock = new ZMQPubSocket();
|
||||||
|
sub_sock = new MSGQSubSocket();
|
||||||
|
}
|
||||||
|
pub_sock->connect(pub_context, endpoint);
|
||||||
|
sub_sock->connect(sub_context, endpoint, ip, false);
|
||||||
|
|
||||||
|
poller->registerSocket(sub_sock);
|
||||||
|
sub2pub[sub_sock] = pub_sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!do_exit) {
|
||||||
|
for (auto sub_sock : poller->poll(100)) {
|
||||||
|
Message * msg = sub_sock->receive();
|
||||||
|
if (msg == NULL) continue;
|
||||||
|
int ret;
|
||||||
|
do {
|
||||||
|
ret = sub2pub[sub_sock]->sendMessage(msg);
|
||||||
|
} while (ret == -1 && errno == EINTR && !do_exit);
|
||||||
|
assert(ret >= 0 || do_exit);
|
||||||
|
delete msg;
|
||||||
|
|
||||||
|
if (do_exit) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
50
cereal/messaging/demo.cc
Normal file
50
cereal/messaging/demo.cc
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "cereal/messaging/messaging.h"
|
||||||
|
#include "cereal/messaging/impl_zmq.h"
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
29
cereal/messaging/demo.py
Normal file
29
cereal/messaging/demo.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
from messaging_pyx import Context, Poller, SubSocket, PubSocket
|
||||||
|
|
||||||
|
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))
|
||||||
236
cereal/messaging/event.cc
Normal file
236
cereal/messaging/event.cc
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <exception>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "cereal/messaging/event.h"
|
||||||
|
|
||||||
|
#ifndef __APPLE__
|
||||||
|
#include <sys/eventfd.h>
|
||||||
|
|
||||||
|
void event_state_shm_mmap(std::string endpoint, std::string identifier, char **shm_mem, std::string *shm_path) {
|
||||||
|
const char* op_prefix = std::getenv("OPENPILOT_PREFIX");
|
||||||
|
|
||||||
|
std::string full_path = "/dev/shm/";
|
||||||
|
if (op_prefix) {
|
||||||
|
full_path += std::string(op_prefix) + "/";
|
||||||
|
}
|
||||||
|
full_path += CEREAL_EVENTS_PREFIX + "/";
|
||||||
|
if (identifier.size() > 0) {
|
||||||
|
full_path += identifier + "/";
|
||||||
|
}
|
||||||
|
std::filesystem::create_directories(full_path);
|
||||||
|
full_path += endpoint;
|
||||||
|
|
||||||
|
int shm_fd = open(full_path.c_str(), O_RDWR | O_CREAT, 0664);
|
||||||
|
if (shm_fd < 0) {
|
||||||
|
throw std::runtime_error("Could not open shared memory file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = ftruncate(shm_fd, sizeof(EventState));
|
||||||
|
if (rc < 0){
|
||||||
|
close(shm_fd);
|
||||||
|
throw std::runtime_error("Could not truncate shared memory file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
char * mem = (char*)mmap(NULL, sizeof(EventState), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
|
||||||
|
close(shm_fd);
|
||||||
|
if (mem == nullptr) {
|
||||||
|
throw std::runtime_error("Could not map shared memory file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shm_mem != nullptr)
|
||||||
|
*shm_mem = mem;
|
||||||
|
if (shm_path != nullptr)
|
||||||
|
*shm_path = full_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketEventHandle::SocketEventHandle(std::string endpoint, std::string identifier, bool override) {
|
||||||
|
char *mem;
|
||||||
|
event_state_shm_mmap(endpoint, identifier, &mem, &this->shm_path);
|
||||||
|
|
||||||
|
this->state = (EventState*)mem;
|
||||||
|
if (override) {
|
||||||
|
this->state->fds[0] = eventfd(0, EFD_NONBLOCK);
|
||||||
|
this->state->fds[1] = eventfd(0, EFD_NONBLOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketEventHandle::~SocketEventHandle() {
|
||||||
|
close(this->state->fds[0]);
|
||||||
|
close(this->state->fds[1]);
|
||||||
|
munmap(this->state, sizeof(EventState));
|
||||||
|
unlink(this->shm_path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SocketEventHandle::is_enabled() {
|
||||||
|
return this->state->enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SocketEventHandle::set_enabled(bool enabled) {
|
||||||
|
this->state->enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
Event SocketEventHandle::recv_called() {
|
||||||
|
return Event(this->state->fds[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Event SocketEventHandle::recv_ready() {
|
||||||
|
return Event(this->state->fds[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SocketEventHandle::toggle_fake_events(bool enabled) {
|
||||||
|
if (enabled)
|
||||||
|
setenv("CEREAL_FAKE", "1", true);
|
||||||
|
else
|
||||||
|
unsetenv("CEREAL_FAKE");
|
||||||
|
}
|
||||||
|
|
||||||
|
void SocketEventHandle::set_fake_prefix(std::string prefix) {
|
||||||
|
if (prefix.size() == 0) {
|
||||||
|
unsetenv("CEREAL_FAKE_PREFIX");
|
||||||
|
} else {
|
||||||
|
setenv("CEREAL_FAKE_PREFIX", prefix.c_str(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SocketEventHandle::fake_prefix() {
|
||||||
|
const char* prefix = std::getenv("CEREAL_FAKE_PREFIX");
|
||||||
|
if (prefix == nullptr) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return std::string(prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::Event(int fd): event_fd(fd) {}
|
||||||
|
|
||||||
|
void Event::set() const {
|
||||||
|
throw_if_invalid();
|
||||||
|
|
||||||
|
uint64_t val = 1;
|
||||||
|
size_t count = write(this->event_fd, &val, sizeof(uint64_t));
|
||||||
|
assert(count == sizeof(uint64_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
int Event::clear() const {
|
||||||
|
throw_if_invalid();
|
||||||
|
|
||||||
|
uint64_t val = 0;
|
||||||
|
// read the eventfd to clear it
|
||||||
|
read(this->event_fd, &val, sizeof(uint64_t));
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Event::wait(int timeout_sec) const {
|
||||||
|
throw_if_invalid();
|
||||||
|
|
||||||
|
int event_count;
|
||||||
|
struct pollfd fds = { this->event_fd, POLLIN, 0 };
|
||||||
|
struct timespec timeout = { timeout_sec, 0 };;
|
||||||
|
|
||||||
|
sigset_t signals;
|
||||||
|
sigfillset(&signals);
|
||||||
|
sigdelset(&signals, SIGALRM);
|
||||||
|
sigdelset(&signals, SIGINT);
|
||||||
|
sigdelset(&signals, SIGTERM);
|
||||||
|
sigdelset(&signals, SIGQUIT);
|
||||||
|
|
||||||
|
event_count = ppoll(&fds, 1, timeout_sec < 0 ? nullptr : &timeout, &signals);
|
||||||
|
|
||||||
|
if (event_count == 0) {
|
||||||
|
throw std::runtime_error("Event timed out pid: " + std::to_string(getpid()));
|
||||||
|
} else if (event_count < 0) {
|
||||||
|
throw std::runtime_error("Event poll failed, errno: " + std::to_string(errno) + " pid: " + std::to_string(getpid()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Event::peek() const {
|
||||||
|
throw_if_invalid();
|
||||||
|
|
||||||
|
int event_count;
|
||||||
|
|
||||||
|
struct pollfd fds = { this->event_fd, POLLIN, 0 };
|
||||||
|
|
||||||
|
// poll with timeout zero to return status immediately
|
||||||
|
event_count = poll(&fds, 1, 0);
|
||||||
|
|
||||||
|
return event_count != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Event::is_valid() const {
|
||||||
|
return event_fd != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Event::fd() const {
|
||||||
|
return event_fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Event::wait_for_one(const std::vector<Event>& events, int timeout_sec) {
|
||||||
|
struct pollfd fds[events.size()];
|
||||||
|
for (size_t i = 0; i < events.size(); i++) {
|
||||||
|
fds[i] = { events[i].fd(), POLLIN, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
struct timespec timeout = { timeout_sec, 0 };
|
||||||
|
|
||||||
|
sigset_t signals;
|
||||||
|
sigfillset(&signals);
|
||||||
|
sigdelset(&signals, SIGALRM);
|
||||||
|
sigdelset(&signals, SIGINT);
|
||||||
|
sigdelset(&signals, SIGTERM);
|
||||||
|
sigdelset(&signals, SIGQUIT);
|
||||||
|
|
||||||
|
int event_count = ppoll(fds, events.size(), timeout_sec < 0 ? nullptr : &timeout, &signals);
|
||||||
|
|
||||||
|
if (event_count == 0) {
|
||||||
|
throw std::runtime_error("Event timed out pid: " + std::to_string(getpid()));
|
||||||
|
} else if (event_count < 0) {
|
||||||
|
throw std::runtime_error("Event poll failed, errno: " + std::to_string(errno) + " pid: " + std::to_string(getpid()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < events.size(); i++) {
|
||||||
|
if (fds[i].revents & POLLIN) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error("Event poll failed, no events ready");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Stub implementation for Darwin, which does not support eventfd
|
||||||
|
void event_state_shm_mmap(std::string endpoint, std::string identifier, char **shm_mem, std::string *shm_path) {}
|
||||||
|
|
||||||
|
SocketEventHandle::SocketEventHandle(std::string endpoint, std::string identifier, bool override) {
|
||||||
|
std::cerr << "SocketEventHandle not supported on macOS" << std::endl;
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
SocketEventHandle::~SocketEventHandle() {}
|
||||||
|
bool SocketEventHandle::is_enabled() { return this->state->enabled; }
|
||||||
|
void SocketEventHandle::set_enabled(bool enabled) {}
|
||||||
|
Event SocketEventHandle::recv_called() { return Event(); }
|
||||||
|
Event SocketEventHandle::recv_ready() { return Event(); }
|
||||||
|
void SocketEventHandle::toggle_fake_events(bool enabled) {}
|
||||||
|
void SocketEventHandle::set_fake_prefix(std::string prefix) {}
|
||||||
|
std::string SocketEventHandle::fake_prefix() { return ""; }
|
||||||
|
|
||||||
|
Event::Event(int fd): event_fd(fd) {}
|
||||||
|
void Event::set() const {}
|
||||||
|
int Event::clear() const { return 0; }
|
||||||
|
void Event::wait(int timeout_sec) const {}
|
||||||
|
bool Event::peek() const { return false; }
|
||||||
|
bool Event::is_valid() const { return false; }
|
||||||
|
int Event::fd() const { return this->event_fd; }
|
||||||
|
int Event::wait_for_one(const std::vector<Event>& events, int timeout_sec) { return -1; }
|
||||||
|
#endif
|
||||||
58
cereal/messaging/event.h
Normal file
58
cereal/messaging/event.h
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define CEREAL_EVENTS_PREFIX std::string("cereal_events")
|
||||||
|
|
||||||
|
void event_state_shm_mmap(std::string endpoint, std::string identifier, char **shm_mem, std::string *shm_path);
|
||||||
|
|
||||||
|
enum EventPurpose {
|
||||||
|
RECV_CALLED,
|
||||||
|
RECV_READY
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EventState {
|
||||||
|
int fds[2];
|
||||||
|
bool enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Event {
|
||||||
|
private:
|
||||||
|
int event_fd = -1;
|
||||||
|
|
||||||
|
inline void throw_if_invalid() const {
|
||||||
|
if (!this->is_valid()) {
|
||||||
|
throw std::runtime_error("Event does not have valid file descriptor.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
Event(int fd = -1);
|
||||||
|
|
||||||
|
void set() const;
|
||||||
|
int clear() const;
|
||||||
|
void wait(int timeout_sec = -1) const;
|
||||||
|
bool peek() const;
|
||||||
|
bool is_valid() const;
|
||||||
|
int fd() const;
|
||||||
|
|
||||||
|
static int wait_for_one(const std::vector<Event>& events, int timeout_sec = -1);
|
||||||
|
};
|
||||||
|
|
||||||
|
class SocketEventHandle {
|
||||||
|
private:
|
||||||
|
std::string shm_path;
|
||||||
|
EventState* state;
|
||||||
|
public:
|
||||||
|
SocketEventHandle(std::string endpoint, std::string identifier = "", bool override = true);
|
||||||
|
~SocketEventHandle();
|
||||||
|
|
||||||
|
bool is_enabled();
|
||||||
|
void set_enabled(bool enabled);
|
||||||
|
Event recv_called();
|
||||||
|
Event recv_ready();
|
||||||
|
|
||||||
|
static void toggle_fake_events(bool enabled);
|
||||||
|
static void set_fake_prefix(std::string prefix);
|
||||||
|
static std::string fake_prefix();
|
||||||
|
};
|
||||||
9
cereal/messaging/impl_fake.cc
Normal file
9
cereal/messaging/impl_fake.cc
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include "cereal/messaging/impl_fake.h"
|
||||||
|
|
||||||
|
void FakePoller::registerSocket(SubSocket *socket) {
|
||||||
|
this->sockets.push_back(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<SubSocket*> FakePoller::poll(int timeout) {
|
||||||
|
return this->sockets;
|
||||||
|
}
|
||||||
67
cereal/messaging/impl_fake.h
Normal file
67
cereal/messaging/impl_fake.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "cereal/messaging/messaging.h"
|
||||||
|
#include "cereal/messaging/event.h"
|
||||||
|
|
||||||
|
template<typename TSubSocket>
|
||||||
|
class FakeSubSocket: public TSubSocket {
|
||||||
|
private:
|
||||||
|
Event *recv_called = nullptr;
|
||||||
|
Event *recv_ready = nullptr;
|
||||||
|
EventState *state = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FakeSubSocket(): TSubSocket() {}
|
||||||
|
~FakeSubSocket() {
|
||||||
|
delete recv_called;
|
||||||
|
delete recv_ready;
|
||||||
|
if (state != nullptr) {
|
||||||
|
munmap(state, sizeof(EventState));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int connect(Context *context, std::string endpoint, std::string address, bool conflate=false, bool check_endpoint=true) override {
|
||||||
|
const char* cereal_prefix = std::getenv("CEREAL_FAKE_PREFIX");
|
||||||
|
|
||||||
|
char* mem;
|
||||||
|
std::string identifier = cereal_prefix != nullptr ? std::string(cereal_prefix) : "";
|
||||||
|
event_state_shm_mmap(endpoint, identifier, &mem, nullptr);
|
||||||
|
|
||||||
|
this->state = (EventState*)mem;
|
||||||
|
this->recv_called = new Event(state->fds[EventPurpose::RECV_CALLED]);
|
||||||
|
this->recv_ready = new Event(state->fds[EventPurpose::RECV_READY]);
|
||||||
|
|
||||||
|
return TSubSocket::connect(context, endpoint, address, conflate, check_endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
Message *receive(bool non_blocking=false) override {
|
||||||
|
if (this->state->enabled) {
|
||||||
|
this->recv_called->set();
|
||||||
|
this->recv_ready->wait();
|
||||||
|
this->recv_ready->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return TSubSocket::receive(non_blocking);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FakePoller: public Poller {
|
||||||
|
private:
|
||||||
|
std::vector<SubSocket*> sockets;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void registerSocket(SubSocket *socket) override;
|
||||||
|
std::vector<SubSocket*> poll(int timeout) override;
|
||||||
|
~FakePoller() {}
|
||||||
|
};
|
||||||
215
cereal/messaging/impl_msgq.cc
Normal file
215
cereal/messaging/impl_msgq.cc
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <csignal>
|
||||||
|
#include <cerrno>
|
||||||
|
|
||||||
|
#include "cereal/services.h"
|
||||||
|
#include "cereal/messaging/impl_msgq.h"
|
||||||
|
|
||||||
|
|
||||||
|
volatile sig_atomic_t msgq_do_exit = 0;
|
||||||
|
|
||||||
|
void sig_handler(int signal) {
|
||||||
|
assert(signal == SIGINT || signal == SIGTERM);
|
||||||
|
msgq_do_exit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool service_exists(std::string path){
|
||||||
|
return services.count(path) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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, bool check_endpoint){
|
||||||
|
assert(context);
|
||||||
|
assert(address == "127.0.0.1");
|
||||||
|
|
||||||
|
if (check_endpoint && !service_exists(std::string(endpoint))){
|
||||||
|
std::cout << "Warning, " << std::string(endpoint) << " is not in service list." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, bool check_endpoint){
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
if (check_endpoint && !service_exists(std::string(endpoint))){
|
||||||
|
std::cout << "Warning, " << std::string(endpoint) << " is not in service list." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
q = new msgq_queue_t;
|
||||||
|
int r = msgq_new_queue(q, endpoint.c_str(), DEFAULT_SEGMENT_SIZE);
|
||||||
|
if (r != 0){
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MSGQPubSocket::all_readers_updated() {
|
||||||
|
return msgq_all_readers_updated(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;
|
||||||
|
}
|
||||||
67
cereal/messaging/impl_msgq.h
Normal file
67
cereal/messaging/impl_msgq.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cereal/messaging/messaging.h"
|
||||||
|
#include "cereal/messaging/msgq.h"
|
||||||
|
|
||||||
|
#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, bool check_endpoint=true);
|
||||||
|
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, bool check_endpoint=true);
|
||||||
|
int sendMessage(Message *message);
|
||||||
|
int send(char *data, size_t size);
|
||||||
|
bool all_readers_updated();
|
||||||
|
~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(){}
|
||||||
|
};
|
||||||
162
cereal/messaging/impl_zmq.cc
Normal file
162
cereal/messaging/impl_zmq.cc
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "cereal/services.h"
|
||||||
|
#include "cereal/messaging/impl_zmq.h"
|
||||||
|
|
||||||
|
static int get_port(std::string endpoint) {
|
||||||
|
return services.at(endpoint).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, bool check_endpoint){
|
||||||
|
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 + ":";
|
||||||
|
if (check_endpoint){
|
||||||
|
full_endpoint += std::to_string(get_port(endpoint));
|
||||||
|
} else {
|
||||||
|
full_endpoint += 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, bool check_endpoint){
|
||||||
|
sock = zmq_socket(context->getRawContext(), ZMQ_PUB);
|
||||||
|
if (sock == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
full_endpoint = "tcp://*:";
|
||||||
|
if (check_endpoint){
|
||||||
|
full_endpoint += std::to_string(get_port(endpoint));
|
||||||
|
} else {
|
||||||
|
full_endpoint += endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZMQ pub sockets cannot be shared between processes, so we need to ensure pid stays the same
|
||||||
|
pid = getpid();
|
||||||
|
|
||||||
|
return zmq_bind(sock, full_endpoint.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
int ZMQPubSocket::sendMessage(Message *message) {
|
||||||
|
assert(pid == getpid());
|
||||||
|
return zmq_send(sock, message->getData(), message->getSize(), ZMQ_DONTWAIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ZMQPubSocket::send(char *data, size_t size) {
|
||||||
|
assert(pid == getpid());
|
||||||
|
return zmq_send(sock, data, size, ZMQ_DONTWAIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ZMQPubSocket::all_readers_updated() {
|
||||||
|
assert(false); // TODO not implemented
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
68
cereal/messaging/impl_zmq.h
Normal file
68
cereal/messaging/impl_zmq.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zmq.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cereal/messaging/messaging.h"
|
||||||
|
|
||||||
|
#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, bool check_endpoint=true);
|
||||||
|
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;
|
||||||
|
int pid = -1;
|
||||||
|
public:
|
||||||
|
int connect(Context *context, std::string endpoint, bool check_endpoint=true);
|
||||||
|
int sendMessage(Message *message);
|
||||||
|
int send(char *data, size_t size);
|
||||||
|
bool all_readers_updated();
|
||||||
|
~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(){}
|
||||||
|
};
|
||||||
120
cereal/messaging/messaging.cc
Normal file
120
cereal/messaging/messaging.cc
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "cereal/messaging/messaging.h"
|
||||||
|
#include "cereal/messaging/impl_zmq.h"
|
||||||
|
#include "cereal/messaging/impl_msgq.h"
|
||||||
|
#include "cereal/messaging/impl_fake.h"
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
const bool MUST_USE_ZMQ = true;
|
||||||
|
#else
|
||||||
|
const bool MUST_USE_ZMQ = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool messaging_use_zmq(){
|
||||||
|
if (std::getenv("ZMQ") || MUST_USE_ZMQ) {
|
||||||
|
if (std::getenv("OPENPILOT_PREFIX")) {
|
||||||
|
std::cerr << "OPENPILOT_PREFIX not supported with ZMQ backend\n";
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool messaging_use_fake(){
|
||||||
|
char* fake_enabled = std::getenv("CEREAL_FAKE");
|
||||||
|
return fake_enabled != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Context * Context::create(){
|
||||||
|
Context * c;
|
||||||
|
if (messaging_use_zmq()){
|
||||||
|
c = new ZMQContext();
|
||||||
|
} else {
|
||||||
|
c = new MSGQContext();
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubSocket * SubSocket::create(){
|
||||||
|
SubSocket * s;
|
||||||
|
if (messaging_use_fake()) {
|
||||||
|
if (messaging_use_zmq()) {
|
||||||
|
s = new FakeSubSocket<ZMQSubSocket>();
|
||||||
|
} else {
|
||||||
|
s = new FakeSubSocket<MSGQSubSocket>();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (messaging_use_zmq()){
|
||||||
|
s = new ZMQSubSocket();
|
||||||
|
} else {
|
||||||
|
s = new MSGQSubSocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubSocket * SubSocket::create(Context * context, std::string endpoint, std::string address, bool conflate, bool check_endpoint){
|
||||||
|
SubSocket *s = SubSocket::create();
|
||||||
|
int r = s->connect(context, endpoint, address, conflate, check_endpoint);
|
||||||
|
|
||||||
|
if (r == 0) {
|
||||||
|
return s;
|
||||||
|
} else {
|
||||||
|
std::cerr << "Error, failed to connect SubSocket to " << endpoint << ": " << strerror(errno) << std::endl;
|
||||||
|
|
||||||
|
delete s;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PubSocket * PubSocket::create(){
|
||||||
|
PubSocket * s;
|
||||||
|
if (messaging_use_zmq()){
|
||||||
|
s = new ZMQPubSocket();
|
||||||
|
} else {
|
||||||
|
s = new MSGQPubSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
PubSocket * PubSocket::create(Context * context, std::string endpoint, bool check_endpoint){
|
||||||
|
PubSocket *s = PubSocket::create();
|
||||||
|
int r = s->connect(context, endpoint, check_endpoint);
|
||||||
|
|
||||||
|
if (r == 0) {
|
||||||
|
return s;
|
||||||
|
} else {
|
||||||
|
std::cerr << "Error, failed to bind PubSocket to " << endpoint << ": " << strerror(errno) << std::endl;
|
||||||
|
|
||||||
|
delete s;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Poller * Poller::create(){
|
||||||
|
Poller * p;
|
||||||
|
if (messaging_use_fake()) {
|
||||||
|
p = new FakePoller();
|
||||||
|
} else {
|
||||||
|
if (messaging_use_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;
|
||||||
|
}
|
||||||
162
cereal/messaging/messaging.h
Normal file
162
cereal/messaging/messaging.h
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <capnp/serialize.h>
|
||||||
|
|
||||||
|
#include "cereal/gen/cpp/log.capnp.h"
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#define CLOCK_BOOTTIME CLOCK_MONOTONIC
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MSG_MULTIPLE_PUBLISHERS 100
|
||||||
|
|
||||||
|
bool messaging_use_zmq();
|
||||||
|
|
||||||
|
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, bool check_endpoint=true) = 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, std::string address="127.0.0.1", bool conflate=false, bool check_endpoint=true);
|
||||||
|
virtual ~SubSocket(){}
|
||||||
|
};
|
||||||
|
|
||||||
|
class PubSocket {
|
||||||
|
public:
|
||||||
|
virtual int connect(Context *context, std::string endpoint, bool check_endpoint=true) = 0;
|
||||||
|
virtual int sendMessage(Message *message) = 0;
|
||||||
|
virtual int send(char *data, size_t size) = 0;
|
||||||
|
virtual bool all_readers_updated() = 0;
|
||||||
|
static PubSocket * create();
|
||||||
|
static PubSocket * create(Context * context, std::string endpoint, bool check_endpoint=true);
|
||||||
|
static PubSocket * create(Context * context, std::string endpoint, int port, bool check_endpoint=true);
|
||||||
|
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(){}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SubMaster {
|
||||||
|
public:
|
||||||
|
SubMaster(const std::vector<const char *> &service_list, const std::vector<const char *> &poll = {},
|
||||||
|
const char *address = nullptr, const std::vector<const char *> &ignore_alive = {});
|
||||||
|
void update(int timeout = 1000);
|
||||||
|
void update_msgs(uint64_t current_time, const std::vector<std::pair<std::string, cereal::Event::Reader>> &messages);
|
||||||
|
inline bool allAlive(const std::vector<const char *> &service_list = {}) { return all_(service_list, false, true); }
|
||||||
|
inline bool allValid(const std::vector<const char *> &service_list = {}) { return all_(service_list, true, false); }
|
||||||
|
inline bool allAliveAndValid(const std::vector<const char *> &service_list = {}) { return all_(service_list, true, true); }
|
||||||
|
void drain();
|
||||||
|
~SubMaster();
|
||||||
|
|
||||||
|
uint64_t frame = 0;
|
||||||
|
bool updated(const char *name) const;
|
||||||
|
bool alive(const char *name) const;
|
||||||
|
bool valid(const char *name) const;
|
||||||
|
uint64_t rcv_frame(const char *name) const;
|
||||||
|
uint64_t rcv_time(const char *name) const;
|
||||||
|
cereal::Event::Reader &operator[](const char *name) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool all_(const std::vector<const char *> &service_list, bool valid, bool alive);
|
||||||
|
Poller *poller_ = nullptr;
|
||||||
|
struct SubMessage;
|
||||||
|
std::map<SubSocket *, SubMessage *> messages_;
|
||||||
|
std::map<std::string, SubMessage *> services_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MessageBuilder : public capnp::MallocMessageBuilder {
|
||||||
|
public:
|
||||||
|
MessageBuilder() = default;
|
||||||
|
|
||||||
|
cereal::Event::Builder initEvent(bool valid = true) {
|
||||||
|
cereal::Event::Builder event = initRoot<cereal::Event>();
|
||||||
|
struct timespec t;
|
||||||
|
clock_gettime(CLOCK_BOOTTIME, &t);
|
||||||
|
uint64_t current_time = t.tv_sec * 1000000000ULL + t.tv_nsec;
|
||||||
|
event.setLogMonoTime(current_time);
|
||||||
|
event.setValid(valid);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::ArrayPtr<capnp::byte> toBytes() {
|
||||||
|
heapArray_ = capnp::messageToFlatArray(*this);
|
||||||
|
return heapArray_.asBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getSerializedSize() {
|
||||||
|
return capnp::computeSerializedSizeInWords(*this) * sizeof(capnp::word);
|
||||||
|
}
|
||||||
|
|
||||||
|
int serializeToBuffer(unsigned char *buffer, size_t buffer_size) {
|
||||||
|
size_t serialized_size = getSerializedSize();
|
||||||
|
if (serialized_size > buffer_size) { return -1; }
|
||||||
|
kj::ArrayOutputStream out(kj::ArrayPtr<capnp::byte>(buffer, buffer_size));
|
||||||
|
capnp::writeMessage(out, *this);
|
||||||
|
return serialized_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
kj::Array<capnp::word> heapArray_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PubMaster {
|
||||||
|
public:
|
||||||
|
PubMaster(const std::vector<const char *> &service_list);
|
||||||
|
inline int send(const char *name, capnp::byte *data, size_t size) { return sockets_.at(name)->send((char *)data, size); }
|
||||||
|
int send(const char *name, MessageBuilder &msg);
|
||||||
|
~PubMaster();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, PubSocket *> sockets_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AlignedBuffer {
|
||||||
|
public:
|
||||||
|
kj::ArrayPtr<const capnp::word> align(const char *data, const size_t size) {
|
||||||
|
words_size = size / sizeof(capnp::word) + 1;
|
||||||
|
if (aligned_buf.size() < words_size) {
|
||||||
|
aligned_buf = kj::heapArray<capnp::word>(words_size < 512 ? 512 : words_size);
|
||||||
|
}
|
||||||
|
memcpy(aligned_buf.begin(), data, size);
|
||||||
|
return aligned_buf.slice(0, words_size);
|
||||||
|
}
|
||||||
|
inline kj::ArrayPtr<const capnp::word> align(Message *m) {
|
||||||
|
return align(m->getData(), m->getSize());
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
kj::Array<capnp::word> aligned_buf;
|
||||||
|
size_t words_size;
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
468
cereal/messaging/msgq.cc
Normal file
468
cereal/messaging/msgq.cc
Normal file
@@ -0,0 +1,468 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <chrono>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <csignal>
|
||||||
|
#include <random>
|
||||||
|
#include <string>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#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 "cereal/messaging/msgq.h"
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
// TODO: this doesn't work
|
||||||
|
uint64_t uid = distribution(rd) << 32 | getpid();
|
||||||
|
#else
|
||||||
|
uint64_t uid = distribution(rd) << 32 | syscall(SYS_gettid);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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){
|
||||||
|
// wait for subscriber
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
std::string full_path = "/dev/shm/";
|
||||||
|
const char* prefix = std::getenv("OPENPILOT_PREFIX");
|
||||||
|
if (prefix) {
|
||||||
|
full_path += std::string(prefix) + "/";
|
||||||
|
}
|
||||||
|
full_path += path;
|
||||||
|
|
||||||
|
auto fd = open(full_path.c_str(), O_RDWR | O_CREAT, 0664);
|
||||||
|
if (fd < 0) {
|
||||||
|
std::cout << "Warning, could not open: " << full_path << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = ftruncate(fd, size + sizeof(msgq_header_t));
|
||||||
|
if (rc < 0){
|
||||||
|
close(fd);
|
||||||
|
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]);
|
||||||
|
UNUSED(read_cycles);
|
||||||
|
|
||||||
|
uint32_t write_cycles, write_pointer;
|
||||||
|
UNPACK64(write_cycles, write_pointer, *q->write_pointer);
|
||||||
|
UNUSED(write_cycles);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
UNUSED(write_cycles);
|
||||||
|
|
||||||
|
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){
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool msgq_all_readers_updated(msgq_queue_t *q) {
|
||||||
|
uint64_t num_readers = *q->num_readers;
|
||||||
|
for (uint64_t i = 0; i < num_readers; i++) {
|
||||||
|
if (*q->read_valids[i] && *q->write_pointer != *q->read_pointers[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num_readers > 0;
|
||||||
|
}
|
||||||
70
cereal/messaging/msgq.h
Normal file
70
cereal/messaging/msgq.h
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#define DEFAULT_SEGMENT_SIZE (10 * 1024 * 1024)
|
||||||
|
#define NUM_READERS 12
|
||||||
|
#define ALIGN(n) ((n + (8 - 1)) & -8)
|
||||||
|
|
||||||
|
#define UNUSED(x) (void)x
|
||||||
|
#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);
|
||||||
|
|
||||||
|
bool msgq_all_readers_updated(msgq_queue_t *q);
|
||||||
54
cereal/messaging/msgq.md
Normal file
54
cereal/messaging/msgq.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# MSGQ: A lock free single producer multi consumer message queue
|
||||||
|
|
||||||
|
## 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.
|
||||||
394
cereal/messaging/msgq_tests.cc
Normal file
394
cereal/messaging/msgq_tests.cc
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
#include "catch2/catch.hpp"
|
||||||
|
#include "cereal/messaging/msgq.h"
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
210
cereal/messaging/socketmaster.cc
Normal file
210
cereal/messaging/socketmaster.cc
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
#include <time.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "cereal/services.h"
|
||||||
|
#include "cereal/messaging/messaging.h"
|
||||||
|
|
||||||
|
const bool SIMULATION = (getenv("SIMULATION") != nullptr) && (std::string(getenv("SIMULATION")) == "1");
|
||||||
|
|
||||||
|
static inline uint64_t nanos_since_boot() {
|
||||||
|
struct timespec t;
|
||||||
|
clock_gettime(CLOCK_BOOTTIME, &t);
|
||||||
|
return t.tv_sec * 1000000000ULL + t.tv_nsec;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool inList(const std::vector<const char *> &list, const char *value) {
|
||||||
|
for (auto &v : list) {
|
||||||
|
if (strcmp(value, v) == 0) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageContext {
|
||||||
|
public:
|
||||||
|
MessageContext() : ctx_(nullptr) {}
|
||||||
|
~MessageContext() { delete ctx_; }
|
||||||
|
inline Context *context() {
|
||||||
|
std::call_once(init_flag, [=]() { ctx_ = Context::create(); });
|
||||||
|
return ctx_;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Context *ctx_;
|
||||||
|
std::once_flag init_flag;
|
||||||
|
};
|
||||||
|
|
||||||
|
MessageContext message_context;
|
||||||
|
|
||||||
|
struct SubMaster::SubMessage {
|
||||||
|
std::string name;
|
||||||
|
SubSocket *socket = nullptr;
|
||||||
|
int freq = 0;
|
||||||
|
bool updated = false, alive = false, valid = true, ignore_alive;
|
||||||
|
uint64_t rcv_time = 0, rcv_frame = 0;
|
||||||
|
void *allocated_msg_reader = nullptr;
|
||||||
|
bool is_polled = false;
|
||||||
|
capnp::FlatArrayMessageReader *msg_reader = nullptr;
|
||||||
|
AlignedBuffer aligned_buf;
|
||||||
|
cereal::Event::Reader event;
|
||||||
|
};
|
||||||
|
|
||||||
|
SubMaster::SubMaster(const std::vector<const char *> &service_list, const std::vector<const char *> &poll,
|
||||||
|
const char *address, const std::vector<const char *> &ignore_alive) {
|
||||||
|
poller_ = Poller::create();
|
||||||
|
for (auto name : service_list) {
|
||||||
|
assert(services.count(std::string(name)) > 0);
|
||||||
|
|
||||||
|
service serv = services.at(std::string(name));
|
||||||
|
SubSocket *socket = SubSocket::create(message_context.context(), name, address ? address : "127.0.0.1", true);
|
||||||
|
assert(socket != 0);
|
||||||
|
bool is_polled = inList(poll, name) || poll.empty();
|
||||||
|
if (is_polled) poller_->registerSocket(socket);
|
||||||
|
SubMessage *m = new SubMessage{
|
||||||
|
.name = name,
|
||||||
|
.socket = socket,
|
||||||
|
.freq = serv.frequency,
|
||||||
|
.ignore_alive = inList(ignore_alive, name),
|
||||||
|
.allocated_msg_reader = malloc(sizeof(capnp::FlatArrayMessageReader)),
|
||||||
|
.is_polled = is_polled};
|
||||||
|
m->msg_reader = new (m->allocated_msg_reader) capnp::FlatArrayMessageReader({});
|
||||||
|
messages_[socket] = m;
|
||||||
|
services_[name] = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SubMaster::update(int timeout) {
|
||||||
|
for (auto &kv : messages_) kv.second->updated = false;
|
||||||
|
|
||||||
|
auto sockets = poller_->poll(timeout);
|
||||||
|
|
||||||
|
// add non-polled sockets for non-blocking receive
|
||||||
|
for (auto &kv : messages_) {
|
||||||
|
SubMessage *m = kv.second;
|
||||||
|
SubSocket *s = kv.first;
|
||||||
|
if (!m->is_polled) sockets.push_back(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t current_time = nanos_since_boot();
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, cereal::Event::Reader>> messages;
|
||||||
|
|
||||||
|
for (auto s : sockets) {
|
||||||
|
Message *msg = s->receive(true);
|
||||||
|
if (msg == nullptr) continue;
|
||||||
|
|
||||||
|
SubMessage *m = messages_.at(s);
|
||||||
|
|
||||||
|
m->msg_reader->~FlatArrayMessageReader();
|
||||||
|
capnp::ReaderOptions options;
|
||||||
|
options.traversalLimitInWords = kj::maxValue; // Don't limit
|
||||||
|
m->msg_reader = new (m->allocated_msg_reader) capnp::FlatArrayMessageReader(m->aligned_buf.align(msg), options);
|
||||||
|
delete msg;
|
||||||
|
messages.push_back({m->name, m->msg_reader->getRoot<cereal::Event>()});
|
||||||
|
}
|
||||||
|
|
||||||
|
update_msgs(current_time, messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SubMaster::update_msgs(uint64_t current_time, const std::vector<std::pair<std::string, cereal::Event::Reader>> &messages){
|
||||||
|
if (++frame == UINT64_MAX) frame = 1;
|
||||||
|
|
||||||
|
for (auto &kv : messages) {
|
||||||
|
auto m_find = services_.find(kv.first);
|
||||||
|
if (m_find == services_.end()){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SubMessage *m = m_find->second;
|
||||||
|
m->event = kv.second;
|
||||||
|
m->updated = true;
|
||||||
|
m->rcv_time = current_time;
|
||||||
|
m->rcv_frame = frame;
|
||||||
|
m->valid = m->event.getValid();
|
||||||
|
if (SIMULATION) m->alive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SIMULATION) {
|
||||||
|
for (auto &kv : messages_) {
|
||||||
|
SubMessage *m = kv.second;
|
||||||
|
m->alive = (m->freq <= (1e-5) || ((current_time - m->rcv_time) * (1e-9)) < (10.0 / m->freq));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SubMaster::all_(const std::vector<const char *> &service_list, bool valid, bool alive) {
|
||||||
|
int found = 0;
|
||||||
|
for (auto &kv : messages_) {
|
||||||
|
SubMessage *m = kv.second;
|
||||||
|
if (service_list.size() == 0 || inList(service_list, m->name.c_str())) {
|
||||||
|
found += (!valid || m->valid) && (!alive || (m->alive || m->ignore_alive));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return service_list.size() == 0 ? found == messages_.size() : found == service_list.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SubMaster::drain() {
|
||||||
|
while (true) {
|
||||||
|
auto polls = poller_->poll(0);
|
||||||
|
if (polls.size() == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
for (auto sock : polls) {
|
||||||
|
Message *msg = sock->receive(true);
|
||||||
|
delete msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SubMaster::updated(const char *name) const {
|
||||||
|
return services_.at(name)->updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SubMaster::alive(const char *name) const {
|
||||||
|
return services_.at(name)->alive;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SubMaster::valid(const char *name) const {
|
||||||
|
return services_.at(name)->valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t SubMaster::rcv_frame(const char *name) const {
|
||||||
|
return services_.at(name)->rcv_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t SubMaster::rcv_time(const char *name) const {
|
||||||
|
return services_.at(name)->rcv_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
cereal::Event::Reader &SubMaster::operator[](const char *name) const {
|
||||||
|
return services_.at(name)->event;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubMaster::~SubMaster() {
|
||||||
|
delete poller_;
|
||||||
|
for (auto &kv : messages_) {
|
||||||
|
SubMessage *m = kv.second;
|
||||||
|
m->msg_reader->~FlatArrayMessageReader();
|
||||||
|
free(m->allocated_msg_reader);
|
||||||
|
delete m->socket;
|
||||||
|
delete m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PubMaster::PubMaster(const std::vector<const char *> &service_list) {
|
||||||
|
for (auto name : service_list) {
|
||||||
|
assert(services.count(name) > 0);
|
||||||
|
PubSocket *socket = PubSocket::create(message_context.context(), name);
|
||||||
|
assert(socket);
|
||||||
|
sockets_[name] = socket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int PubMaster::send(const char *name, MessageBuilder &msg) {
|
||||||
|
auto bytes = msg.toBytes();
|
||||||
|
return send(name, bytes.begin(), bytes.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
PubMaster::~PubMaster() {
|
||||||
|
for (auto s : sockets_) delete s.second;
|
||||||
|
}
|
||||||
14
cereal/messaging/stress.py
Normal file
14
cereal/messaging/stress.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from messaging_pyx import Context, SubSocket, PubSocket
|
||||||
|
|
||||||
|
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())
|
||||||
2
cereal/messaging/test_runner.cc
Normal file
2
cereal/messaging/test_runner.cc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
0
cereal/messaging/tests/__init__.py
Normal file
0
cereal/messaging/tests/__init__.py
Normal file
193
cereal/messaging/tests/test_fake.py
Normal file
193
cereal/messaging/tests/test_fake.py
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
import multiprocessing
|
||||||
|
import platform
|
||||||
|
from parameterized import parameterized_class
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import cereal.messaging as messaging
|
||||||
|
|
||||||
|
WAIT_TIMEOUT = 5
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(platform.system() == "Darwin", "Events not supported on macOS")
|
||||||
|
class TestEvents(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_mutation(self):
|
||||||
|
handle = messaging.fake_event_handle("carState")
|
||||||
|
event = handle.recv_called_event
|
||||||
|
|
||||||
|
self.assertFalse(event.peek())
|
||||||
|
event.set()
|
||||||
|
self.assertTrue(event.peek())
|
||||||
|
event.clear()
|
||||||
|
self.assertFalse(event.peek())
|
||||||
|
|
||||||
|
del event
|
||||||
|
|
||||||
|
def test_wait(self):
|
||||||
|
handle = messaging.fake_event_handle("carState")
|
||||||
|
event = handle.recv_called_event
|
||||||
|
|
||||||
|
event.set()
|
||||||
|
try:
|
||||||
|
event.wait(WAIT_TIMEOUT)
|
||||||
|
self.assertTrue(event.peek())
|
||||||
|
except RuntimeError:
|
||||||
|
self.fail("event.wait() timed out")
|
||||||
|
|
||||||
|
def test_wait_multiprocess(self):
|
||||||
|
handle = messaging.fake_event_handle("carState")
|
||||||
|
event = handle.recv_called_event
|
||||||
|
|
||||||
|
def set_event_run():
|
||||||
|
event.set()
|
||||||
|
|
||||||
|
try:
|
||||||
|
p = multiprocessing.Process(target=set_event_run)
|
||||||
|
p.start()
|
||||||
|
event.wait(WAIT_TIMEOUT)
|
||||||
|
self.assertTrue(event.peek())
|
||||||
|
except RuntimeError:
|
||||||
|
self.fail("event.wait() timed out")
|
||||||
|
|
||||||
|
p.kill()
|
||||||
|
|
||||||
|
def test_wait_zero_timeout(self):
|
||||||
|
handle = messaging.fake_event_handle("carState")
|
||||||
|
event = handle.recv_called_event
|
||||||
|
|
||||||
|
try:
|
||||||
|
event.wait(0)
|
||||||
|
self.fail("event.wait() did not time out")
|
||||||
|
except RuntimeError:
|
||||||
|
self.assertFalse(event.peek())
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(platform.system() == "Darwin", "FakeSockets not supported on macOS")
|
||||||
|
@unittest.skipIf("ZMQ" in os.environ, "FakeSockets not supported on ZMQ")
|
||||||
|
@parameterized_class([{"prefix": None}, {"prefix": "test"}])
|
||||||
|
class TestFakeSockets(unittest.TestCase):
|
||||||
|
prefix: Optional[str] = None
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
messaging.toggle_fake_events(True)
|
||||||
|
if self.prefix is not None:
|
||||||
|
messaging.set_fake_prefix(self.prefix)
|
||||||
|
else:
|
||||||
|
messaging.delete_fake_prefix()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
messaging.toggle_fake_events(False)
|
||||||
|
messaging.delete_fake_prefix()
|
||||||
|
|
||||||
|
def test_event_handle_init(self):
|
||||||
|
handle = messaging.fake_event_handle("controlsState", override=True)
|
||||||
|
|
||||||
|
self.assertFalse(handle.enabled)
|
||||||
|
self.assertGreaterEqual(handle.recv_called_event.fd, 0)
|
||||||
|
self.assertGreaterEqual(handle.recv_ready_event.fd, 0)
|
||||||
|
|
||||||
|
def test_non_managed_socket_state(self):
|
||||||
|
# non managed socket should have zero state
|
||||||
|
_ = messaging.pub_sock("ubloxGnss")
|
||||||
|
|
||||||
|
handle = messaging.fake_event_handle("ubloxGnss", override=False)
|
||||||
|
|
||||||
|
self.assertFalse(handle.enabled)
|
||||||
|
self.assertEqual(handle.recv_called_event.fd, 0)
|
||||||
|
self.assertEqual(handle.recv_ready_event.fd, 0)
|
||||||
|
|
||||||
|
def test_managed_socket_state(self):
|
||||||
|
# managed socket should not change anything about the state
|
||||||
|
handle = messaging.fake_event_handle("ubloxGnss")
|
||||||
|
handle.enabled = True
|
||||||
|
|
||||||
|
expected_enabled = handle.enabled
|
||||||
|
expected_recv_called_fd = handle.recv_called_event.fd
|
||||||
|
expected_recv_ready_fd = handle.recv_ready_event.fd
|
||||||
|
|
||||||
|
_ = messaging.pub_sock("ubloxGnss")
|
||||||
|
|
||||||
|
self.assertEqual(handle.enabled, expected_enabled)
|
||||||
|
self.assertEqual(handle.recv_called_event.fd, expected_recv_called_fd)
|
||||||
|
self.assertEqual(handle.recv_ready_event.fd, expected_recv_ready_fd)
|
||||||
|
|
||||||
|
def test_sockets_enable_disable(self):
|
||||||
|
carState_handle = messaging.fake_event_handle("ubloxGnss", enable=True)
|
||||||
|
recv_called = carState_handle.recv_called_event
|
||||||
|
recv_ready = carState_handle.recv_ready_event
|
||||||
|
|
||||||
|
pub_sock = messaging.pub_sock("ubloxGnss")
|
||||||
|
sub_sock = messaging.sub_sock("ubloxGnss")
|
||||||
|
|
||||||
|
try:
|
||||||
|
carState_handle.enabled = True
|
||||||
|
recv_ready.set()
|
||||||
|
pub_sock.send(b"test")
|
||||||
|
_ = sub_sock.receive()
|
||||||
|
self.assertTrue(recv_called.peek())
|
||||||
|
recv_called.clear()
|
||||||
|
|
||||||
|
carState_handle.enabled = False
|
||||||
|
recv_ready.set()
|
||||||
|
pub_sock.send(b"test")
|
||||||
|
_ = sub_sock.receive()
|
||||||
|
self.assertFalse(recv_called.peek())
|
||||||
|
except RuntimeError:
|
||||||
|
self.fail("event.wait() timed out")
|
||||||
|
|
||||||
|
def test_synced_pub_sub(self):
|
||||||
|
def daemon_repub_process_run():
|
||||||
|
pub_sock = messaging.pub_sock("ubloxGnss")
|
||||||
|
sub_sock = messaging.sub_sock("carState")
|
||||||
|
|
||||||
|
frame = -1
|
||||||
|
while True:
|
||||||
|
frame += 1
|
||||||
|
msg = sub_sock.receive(non_blocking=True)
|
||||||
|
if msg is None:
|
||||||
|
print("none received")
|
||||||
|
continue
|
||||||
|
|
||||||
|
bts = frame.to_bytes(8, 'little')
|
||||||
|
pub_sock.send(bts)
|
||||||
|
|
||||||
|
carState_handle = messaging.fake_event_handle("carState", enable=True)
|
||||||
|
recv_called = carState_handle.recv_called_event
|
||||||
|
recv_ready = carState_handle.recv_ready_event
|
||||||
|
|
||||||
|
p = multiprocessing.Process(target=daemon_repub_process_run)
|
||||||
|
p.start()
|
||||||
|
|
||||||
|
pub_sock = messaging.pub_sock("carState")
|
||||||
|
sub_sock = messaging.sub_sock("ubloxGnss")
|
||||||
|
|
||||||
|
try:
|
||||||
|
for i in range(10):
|
||||||
|
recv_called.wait(WAIT_TIMEOUT)
|
||||||
|
recv_called.clear()
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
sub_sock.receive(non_blocking=True)
|
||||||
|
|
||||||
|
bts = i.to_bytes(8, 'little')
|
||||||
|
pub_sock.send(bts)
|
||||||
|
|
||||||
|
recv_ready.set()
|
||||||
|
recv_called.wait(WAIT_TIMEOUT)
|
||||||
|
|
||||||
|
msg = sub_sock.receive(non_blocking=True)
|
||||||
|
self.assertIsNotNone(msg)
|
||||||
|
self.assertEqual(len(msg), 8)
|
||||||
|
|
||||||
|
frame = int.from_bytes(msg, 'little')
|
||||||
|
self.assertEqual(frame, i)
|
||||||
|
except RuntimeError:
|
||||||
|
self.fail("event.wait() timed out")
|
||||||
|
finally:
|
||||||
|
p.kill()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
242
cereal/messaging/tests/test_messaging.py
Normal file
242
cereal/messaging/tests/test_messaging.py
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import capnp
|
||||||
|
import multiprocessing
|
||||||
|
import numbers
|
||||||
|
import random
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
from parameterized import parameterized
|
||||||
|
|
||||||
|
from cereal import log, car
|
||||||
|
import cereal.messaging as messaging
|
||||||
|
from cereal.services import SERVICE_LIST
|
||||||
|
|
||||||
|
events = [evt for evt in log.Event.schema.union_fields if evt in SERVICE_LIST.keys()]
|
||||||
|
|
||||||
|
def random_sock():
|
||||||
|
return random.choice(events)
|
||||||
|
|
||||||
|
def random_socks(num_socks=10):
|
||||||
|
return list({random_sock() for _ in range(num_socks)})
|
||||||
|
|
||||||
|
def random_bytes(length=1000):
|
||||||
|
return bytes([random.randrange(0xFF) for _ in range(length)])
|
||||||
|
|
||||||
|
def zmq_sleep(t=1):
|
||||||
|
if "ZMQ" in os.environ:
|
||||||
|
time.sleep(t)
|
||||||
|
|
||||||
|
def zmq_expected_failure(func):
|
||||||
|
if "ZMQ" in os.environ:
|
||||||
|
return unittest.expectedFailure(func)
|
||||||
|
else:
|
||||||
|
return func
|
||||||
|
|
||||||
|
# TODO: this should take any capnp struct and returrn a msg with random populated data
|
||||||
|
def random_carstate():
|
||||||
|
fields = ["vEgo", "aEgo", "gas", "steeringAngleDeg"]
|
||||||
|
msg = messaging.new_message("carState")
|
||||||
|
cs = msg.carState
|
||||||
|
for f in fields:
|
||||||
|
setattr(cs, f, random.random() * 10)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
# TODO: this should compare any capnp structs
|
||||||
|
def assert_carstate(cs1, cs2):
|
||||||
|
for f in car.CarState.schema.non_union_fields:
|
||||||
|
# TODO: check all types
|
||||||
|
val1, val2 = getattr(cs1, f), getattr(cs2, f)
|
||||||
|
if isinstance(val1, numbers.Number):
|
||||||
|
assert val1 == val2, f"{f}: sent '{val1}' vs recvd '{val2}'"
|
||||||
|
|
||||||
|
def delayed_send(delay, sock, dat):
|
||||||
|
def send_func():
|
||||||
|
sock.send(dat)
|
||||||
|
threading.Timer(delay, send_func).start()
|
||||||
|
|
||||||
|
class TestPubSubSockets(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# ZMQ pub socket takes too long to die
|
||||||
|
# sleep to prevent multiple publishers error between tests
|
||||||
|
zmq_sleep()
|
||||||
|
|
||||||
|
def test_pub_sub(self):
|
||||||
|
sock = random_sock()
|
||||||
|
pub_sock = messaging.pub_sock(sock)
|
||||||
|
sub_sock = messaging.sub_sock(sock, conflate=False, timeout=None)
|
||||||
|
zmq_sleep(3)
|
||||||
|
|
||||||
|
for _ in range(1000):
|
||||||
|
msg = random_bytes()
|
||||||
|
pub_sock.send(msg)
|
||||||
|
recvd = sub_sock.receive()
|
||||||
|
self.assertEqual(msg, recvd)
|
||||||
|
|
||||||
|
def test_conflate(self):
|
||||||
|
sock = random_sock()
|
||||||
|
pub_sock = messaging.pub_sock(sock)
|
||||||
|
for conflate in [True, False]:
|
||||||
|
for _ in range(10):
|
||||||
|
num_msgs = random.randint(3, 10)
|
||||||
|
sub_sock = messaging.sub_sock(sock, conflate=conflate, timeout=None)
|
||||||
|
zmq_sleep()
|
||||||
|
|
||||||
|
sent_msgs = []
|
||||||
|
for __ in range(num_msgs):
|
||||||
|
msg = random_bytes()
|
||||||
|
pub_sock.send(msg)
|
||||||
|
sent_msgs.append(msg)
|
||||||
|
time.sleep(0.1)
|
||||||
|
recvd_msgs = messaging.drain_sock_raw(sub_sock)
|
||||||
|
if conflate:
|
||||||
|
self.assertEqual(len(recvd_msgs), 1)
|
||||||
|
else:
|
||||||
|
# TODO: compare actual data
|
||||||
|
self.assertEqual(len(recvd_msgs), len(sent_msgs))
|
||||||
|
|
||||||
|
def test_receive_timeout(self):
|
||||||
|
sock = random_sock()
|
||||||
|
for _ in range(10):
|
||||||
|
timeout = random.randrange(200)
|
||||||
|
sub_sock = messaging.sub_sock(sock, timeout=timeout)
|
||||||
|
zmq_sleep()
|
||||||
|
|
||||||
|
start_time = time.monotonic()
|
||||||
|
recvd = sub_sock.receive()
|
||||||
|
self.assertLess(time.monotonic() - start_time, 0.2)
|
||||||
|
assert recvd is None
|
||||||
|
|
||||||
|
class TestMessaging(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# ZMQ pub socket takes too long to die
|
||||||
|
# sleep to prevent multiple publishers error between tests
|
||||||
|
zmq_sleep()
|
||||||
|
|
||||||
|
@parameterized.expand(events)
|
||||||
|
def test_new_message(self, evt):
|
||||||
|
try:
|
||||||
|
msg = messaging.new_message(evt)
|
||||||
|
except capnp.lib.capnp.KjException:
|
||||||
|
msg = messaging.new_message(evt, random.randrange(200))
|
||||||
|
self.assertLess(time.monotonic() - msg.logMonoTime, 0.1)
|
||||||
|
self.assertFalse(msg.valid)
|
||||||
|
self.assertEqual(evt, msg.which())
|
||||||
|
|
||||||
|
@parameterized.expand(events)
|
||||||
|
def test_pub_sock(self, evt):
|
||||||
|
messaging.pub_sock(evt)
|
||||||
|
|
||||||
|
@parameterized.expand(events)
|
||||||
|
def test_sub_sock(self, evt):
|
||||||
|
messaging.sub_sock(evt)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
(messaging.drain_sock, capnp._DynamicStructReader),
|
||||||
|
(messaging.drain_sock_raw, bytes),
|
||||||
|
])
|
||||||
|
def test_drain_sock(self, func, expected_type):
|
||||||
|
sock = "carState"
|
||||||
|
pub_sock = messaging.pub_sock(sock)
|
||||||
|
sub_sock = messaging.sub_sock(sock, timeout=1000)
|
||||||
|
zmq_sleep()
|
||||||
|
|
||||||
|
# no wait and no msgs in queue
|
||||||
|
msgs = func(sub_sock)
|
||||||
|
self.assertIsInstance(msgs, list)
|
||||||
|
self.assertEqual(len(msgs), 0)
|
||||||
|
|
||||||
|
# no wait but msgs are queued up
|
||||||
|
num_msgs = random.randrange(3, 10)
|
||||||
|
for _ in range(num_msgs):
|
||||||
|
pub_sock.send(messaging.new_message(sock).to_bytes())
|
||||||
|
time.sleep(0.1)
|
||||||
|
msgs = func(sub_sock)
|
||||||
|
self.assertIsInstance(msgs, list)
|
||||||
|
self.assertTrue(all(isinstance(msg, expected_type) for msg in msgs))
|
||||||
|
self.assertEqual(len(msgs), num_msgs)
|
||||||
|
|
||||||
|
def test_recv_sock(self):
|
||||||
|
sock = "carState"
|
||||||
|
pub_sock = messaging.pub_sock(sock)
|
||||||
|
sub_sock = messaging.sub_sock(sock, timeout=100)
|
||||||
|
zmq_sleep()
|
||||||
|
|
||||||
|
# no wait and no msg in queue, socket should timeout
|
||||||
|
recvd = messaging.recv_sock(sub_sock)
|
||||||
|
self.assertTrue(recvd is None)
|
||||||
|
|
||||||
|
# no wait and one msg in queue
|
||||||
|
msg = random_carstate()
|
||||||
|
pub_sock.send(msg.to_bytes())
|
||||||
|
time.sleep(0.01)
|
||||||
|
recvd = messaging.recv_sock(sub_sock)
|
||||||
|
self.assertIsInstance(recvd, capnp._DynamicStructReader)
|
||||||
|
# https://github.com/python/mypy/issues/13038
|
||||||
|
assert_carstate(msg.carState, recvd.carState) # type: ignore[union-attr]
|
||||||
|
|
||||||
|
def test_recv_one(self):
|
||||||
|
sock = "carState"
|
||||||
|
pub_sock = messaging.pub_sock(sock)
|
||||||
|
sub_sock = messaging.sub_sock(sock, timeout=1000)
|
||||||
|
zmq_sleep()
|
||||||
|
|
||||||
|
# no msg in queue, socket should timeout
|
||||||
|
recvd = messaging.recv_one(sub_sock)
|
||||||
|
self.assertTrue(recvd is None)
|
||||||
|
|
||||||
|
# one msg in queue
|
||||||
|
msg = random_carstate()
|
||||||
|
pub_sock.send(msg.to_bytes())
|
||||||
|
recvd = messaging.recv_one(sub_sock)
|
||||||
|
self.assertIsInstance(recvd, capnp._DynamicStructReader)
|
||||||
|
assert_carstate(msg.carState, recvd.carState) # type: ignore[union-attr]
|
||||||
|
|
||||||
|
@zmq_expected_failure
|
||||||
|
def test_recv_one_or_none(self):
|
||||||
|
sock = "carState"
|
||||||
|
pub_sock = messaging.pub_sock(sock)
|
||||||
|
sub_sock = messaging.sub_sock(sock)
|
||||||
|
zmq_sleep()
|
||||||
|
|
||||||
|
# no msg in queue, socket shouldn't block
|
||||||
|
recvd = messaging.recv_one_or_none(sub_sock)
|
||||||
|
self.assertTrue(recvd is None)
|
||||||
|
|
||||||
|
# one msg in queue
|
||||||
|
msg = random_carstate()
|
||||||
|
pub_sock.send(msg.to_bytes())
|
||||||
|
recvd = messaging.recv_one_or_none(sub_sock)
|
||||||
|
self.assertIsInstance(recvd, capnp._DynamicStructReader)
|
||||||
|
assert_carstate(msg.carState, recvd.carState) # type: ignore[union-attr]
|
||||||
|
|
||||||
|
def test_recv_one_retry(self):
|
||||||
|
sock = "carState"
|
||||||
|
sock_timeout = 0.1
|
||||||
|
pub_sock = messaging.pub_sock(sock)
|
||||||
|
sub_sock = messaging.sub_sock(sock, timeout=round(sock_timeout*1000))
|
||||||
|
zmq_sleep()
|
||||||
|
|
||||||
|
# this test doesn't work with ZMQ since multiprocessing interrupts it
|
||||||
|
if "ZMQ" not in os.environ:
|
||||||
|
# wait 15 socket timeouts and make sure it's still retrying
|
||||||
|
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
|
||||||
|
p.start()
|
||||||
|
time.sleep(sock_timeout*15)
|
||||||
|
self.assertTrue(p.is_alive())
|
||||||
|
p.terminate()
|
||||||
|
|
||||||
|
# wait 15 socket timeouts before sending
|
||||||
|
msg = random_carstate()
|
||||||
|
delayed_send(sock_timeout*15, pub_sock, msg.to_bytes())
|
||||||
|
start_time = time.monotonic()
|
||||||
|
recvd = messaging.recv_one_retry(sub_sock)
|
||||||
|
self.assertGreaterEqual(time.monotonic() - start_time, sock_timeout*15)
|
||||||
|
self.assertIsInstance(recvd, capnp._DynamicStructReader)
|
||||||
|
assert_carstate(msg.carState, recvd.carState)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
142
cereal/messaging/tests/test_poller.py
Normal file
142
cereal/messaging/tests/test_poller.py
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
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(b"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(b"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(b"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(1, 100):
|
||||||
|
pub.send(b'a'*i)
|
||||||
|
|
||||||
|
msg_seen = False
|
||||||
|
i = 1
|
||||||
|
while True:
|
||||||
|
r = sub.receive(non_blocking=True)
|
||||||
|
|
||||||
|
if r is not None:
|
||||||
|
self.assertEqual(b'a'*i, r)
|
||||||
|
|
||||||
|
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(b'a')
|
||||||
|
pub.send(b'b')
|
||||||
|
|
||||||
|
self.assertEqual(b'b', sub.receive())
|
||||||
|
|
||||||
|
del pub
|
||||||
|
del sub
|
||||||
|
context.term()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
163
cereal/messaging/tests/test_pub_sub_master.py
Normal file
163
cereal/messaging/tests/test_pub_sub_master.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
from typing import Sized, cast
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import cereal.messaging as messaging
|
||||||
|
from cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
|
||||||
|
random_bytes, random_carstate, assert_carstate, \
|
||||||
|
zmq_sleep
|
||||||
|
|
||||||
|
|
||||||
|
class TestSubMaster(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# ZMQ pub socket takes too long to die
|
||||||
|
# sleep to prevent multiple publishers error between tests
|
||||||
|
zmq_sleep(3)
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
sm = messaging.SubMaster(events)
|
||||||
|
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
|
||||||
|
sm.sock, sm.data, sm.logMonoTime, sm.valid]:
|
||||||
|
self.assertEqual(len(cast(Sized, p)), len(events))
|
||||||
|
|
||||||
|
def test_init_state(self):
|
||||||
|
socks = random_socks()
|
||||||
|
sm = messaging.SubMaster(socks)
|
||||||
|
self.assertEqual(sm.frame, -1)
|
||||||
|
self.assertFalse(any(sm.updated.values()))
|
||||||
|
self.assertFalse(any(sm.alive.values()))
|
||||||
|
self.assertTrue(all(t == 0. for t in sm.recv_time.values()))
|
||||||
|
self.assertTrue(all(f == 0 for f in sm.recv_frame.values()))
|
||||||
|
self.assertTrue(all(t == 0 for t in sm.logMonoTime.values()))
|
||||||
|
|
||||||
|
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
|
||||||
|
sm.sock, sm.data, sm.logMonoTime, sm.valid]:
|
||||||
|
self.assertEqual(len(cast(Sized, p)), len(socks))
|
||||||
|
|
||||||
|
def test_getitem(self):
|
||||||
|
sock = "carState"
|
||||||
|
pub_sock = messaging.pub_sock(sock)
|
||||||
|
sm = messaging.SubMaster([sock,])
|
||||||
|
zmq_sleep()
|
||||||
|
|
||||||
|
msg = random_carstate()
|
||||||
|
pub_sock.send(msg.to_bytes())
|
||||||
|
sm.update(1000)
|
||||||
|
assert_carstate(msg.carState, sm[sock])
|
||||||
|
|
||||||
|
# TODO: break this test up to individually test SubMaster.update and SubMaster.update_msgs
|
||||||
|
def test_update(self):
|
||||||
|
sock = "carState"
|
||||||
|
pub_sock = messaging.pub_sock(sock)
|
||||||
|
sm = messaging.SubMaster([sock,])
|
||||||
|
zmq_sleep()
|
||||||
|
|
||||||
|
for i in range(10):
|
||||||
|
msg = messaging.new_message(sock)
|
||||||
|
pub_sock.send(msg.to_bytes())
|
||||||
|
sm.update(1000)
|
||||||
|
self.assertEqual(sm.frame, i)
|
||||||
|
self.assertTrue(all(sm.updated.values()))
|
||||||
|
|
||||||
|
def test_update_timeout(self):
|
||||||
|
sock = random_sock()
|
||||||
|
sm = messaging.SubMaster([sock,])
|
||||||
|
for _ in range(5):
|
||||||
|
timeout = random.randrange(1000, 5000)
|
||||||
|
start_time = time.monotonic()
|
||||||
|
sm.update(timeout)
|
||||||
|
t = time.monotonic() - start_time
|
||||||
|
self.assertGreaterEqual(t, timeout/1000.)
|
||||||
|
self.assertLess(t, 5)
|
||||||
|
self.assertFalse(any(sm.updated.values()))
|
||||||
|
|
||||||
|
def test_avg_frequency_checks(self):
|
||||||
|
for poll in (True, False):
|
||||||
|
sm = messaging.SubMaster(["modelV2", "carParams", "carState", "cameraOdometry", "liveCalibration"],
|
||||||
|
poll=("modelV2" if poll else None),
|
||||||
|
frequency=(20. if not poll else None))
|
||||||
|
|
||||||
|
checks = {
|
||||||
|
"carState": (20, 20),
|
||||||
|
"modelV2": (20, 20 if poll else 10),
|
||||||
|
"cameraOdometry": (20, 10),
|
||||||
|
"liveCalibration": (4, 4),
|
||||||
|
"carParams": (None, None),
|
||||||
|
}
|
||||||
|
|
||||||
|
for service, (max_freq, min_freq) in checks.items():
|
||||||
|
if max_freq is not None:
|
||||||
|
assert sm._check_avg_freq(service)
|
||||||
|
assert sm.max_freq[service] == max_freq*1.2
|
||||||
|
assert sm.min_freq[service] == min_freq*0.8
|
||||||
|
else:
|
||||||
|
assert not sm._check_avg_freq(service)
|
||||||
|
|
||||||
|
def test_alive(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_ignore_alive(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_valid(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# SubMaster should always conflate
|
||||||
|
def test_conflate(self):
|
||||||
|
sock = "carState"
|
||||||
|
pub_sock = messaging.pub_sock(sock)
|
||||||
|
sm = messaging.SubMaster([sock,])
|
||||||
|
|
||||||
|
n = 10
|
||||||
|
for i in range(n+1):
|
||||||
|
msg = messaging.new_message(sock)
|
||||||
|
msg.carState.vEgo = i
|
||||||
|
pub_sock.send(msg.to_bytes())
|
||||||
|
time.sleep(0.01)
|
||||||
|
sm.update(1000)
|
||||||
|
self.assertEqual(sm[sock].vEgo, n)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPubMaster(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# ZMQ pub socket takes too long to die
|
||||||
|
# sleep to prevent multiple publishers error between tests
|
||||||
|
zmq_sleep(3)
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
messaging.PubMaster(events)
|
||||||
|
|
||||||
|
def test_send(self):
|
||||||
|
socks = random_socks()
|
||||||
|
pm = messaging.PubMaster(socks)
|
||||||
|
sub_socks = {s: messaging.sub_sock(s, conflate=True, timeout=1000) for s in socks}
|
||||||
|
zmq_sleep()
|
||||||
|
|
||||||
|
# PubMaster accepts either a capnp msg builder or bytes
|
||||||
|
for capnp in [True, False]:
|
||||||
|
for i in range(100):
|
||||||
|
sock = socks[i % len(socks)]
|
||||||
|
|
||||||
|
if capnp:
|
||||||
|
try:
|
||||||
|
msg = messaging.new_message(sock)
|
||||||
|
except Exception:
|
||||||
|
msg = messaging.new_message(sock, random.randrange(50))
|
||||||
|
else:
|
||||||
|
msg = random_bytes()
|
||||||
|
|
||||||
|
pm.send(sock, msg)
|
||||||
|
recvd = sub_socks[sock].receive()
|
||||||
|
|
||||||
|
if capnp:
|
||||||
|
msg.clear_write_flag()
|
||||||
|
msg = msg.to_bytes()
|
||||||
|
self.assertEqual(msg, recvd, i)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
33
cereal/messaging/tests/test_services.py
Normal file
33
cereal/messaging/tests/test_services.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from typing import Dict
|
||||||
|
import unittest
|
||||||
|
from parameterized import parameterized
|
||||||
|
|
||||||
|
import cereal.services as services
|
||||||
|
from cereal.services import SERVICE_LIST, RESERVED_PORT, STARTING_PORT
|
||||||
|
|
||||||
|
|
||||||
|
class TestServices(unittest.TestCase):
|
||||||
|
|
||||||
|
@parameterized.expand(SERVICE_LIST.keys())
|
||||||
|
def test_services(self, s):
|
||||||
|
service = SERVICE_LIST[s]
|
||||||
|
self.assertTrue(service.port != RESERVED_PORT)
|
||||||
|
self.assertTrue(service.port >= STARTING_PORT)
|
||||||
|
self.assertTrue(service.frequency <= 104)
|
||||||
|
|
||||||
|
def test_no_duplicate_port(self):
|
||||||
|
ports: Dict[int, str] = {}
|
||||||
|
for name, service in SERVICE_LIST.items():
|
||||||
|
self.assertFalse(service.port in ports.keys(), f"duplicate port {service.port}")
|
||||||
|
ports[service.port] = name
|
||||||
|
|
||||||
|
def test_generated_header(self):
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".h") as f:
|
||||||
|
ret = os.system(f"python3 {services.__file__} > {f.name} && clang++ {f.name}")
|
||||||
|
self.assertEqual(ret, 0, "generated services header is not valid C")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
21
cereal/pyproject.toml
Normal file
21
cereal/pyproject.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml
|
||||||
|
[tool.ruff]
|
||||||
|
lint.select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF100", "A"]
|
||||||
|
lint.ignore = ["W292", "E741", "E402", "C408", "ISC003"]
|
||||||
|
lint.flake8-implicit-str-concat.allow-multiline=false
|
||||||
|
|
||||||
|
line-length = 160
|
||||||
|
target-version="py311"
|
||||||
|
|
||||||
|
[mypy.tool]
|
||||||
|
# third-party packages
|
||||||
|
ignore_missing_imports=true
|
||||||
|
|
||||||
|
# helpful warnings
|
||||||
|
warn_redundant_casts=true
|
||||||
|
warn_unreachable=true
|
||||||
|
warn_unused_ignores=true
|
||||||
|
|
||||||
|
# restrict dynamic typing
|
||||||
|
warn_return_any=true
|
||||||
|
check_untyped_defs=true
|
||||||
@@ -46,6 +46,7 @@ services: dict[str, tuple] = {
|
|||||||
"androidLog": (True, 0.),
|
"androidLog": (True, 0.),
|
||||||
"carState": (True, 100., 10),
|
"carState": (True, 100., 10),
|
||||||
"carControl": (True, 100., 10),
|
"carControl": (True, 100., 10),
|
||||||
|
"carOutput": (True, 100., 10),
|
||||||
"longitudinalPlan": (True, 20., 5),
|
"longitudinalPlan": (True, 20., 5),
|
||||||
"procLog": (True, 0.5, 15),
|
"procLog": (True, 0.5, 15),
|
||||||
"gpsLocationExternal": (True, 10., 10),
|
"gpsLocationExternal": (True, 10., 10),
|
||||||
@@ -83,6 +84,7 @@ services: dict[str, tuple] = {
|
|||||||
|
|
||||||
# FrogPilot
|
# FrogPilot
|
||||||
"frogpilotCarControl": (True, 100., 10),
|
"frogpilotCarControl": (True, 100., 10),
|
||||||
|
"frogpilotCarState": (True, 100., 10),
|
||||||
"frogpilotDeviceState": (True, 2., 1),
|
"frogpilotDeviceState": (True, 2., 1),
|
||||||
"frogpilotNavigation": (True, 1., 10),
|
"frogpilotNavigation": (True, 1., 10),
|
||||||
"frogpilotPlan": (True, 20., 5),
|
"frogpilotPlan": (True, 20., 5),
|
||||||
|
|||||||
72
cereal/site_scons/site_tools/cython.py
Normal file
72
cereal/site_scons/site_tools/cython.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import re
|
||||||
|
import SCons
|
||||||
|
from SCons.Action import Action
|
||||||
|
from SCons.Scanner import Scanner
|
||||||
|
|
||||||
|
pyx_from_import_re = re.compile(r'^from\s+(\S+)\s+cimport', re.M)
|
||||||
|
pyx_import_re = re.compile(r'^cimport\s+(\S+)', re.M)
|
||||||
|
cdef_import_re = re.compile(r'^cdef extern from\s+.(\S+).:', re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def pyx_scan(node, env, path, arg=None):
|
||||||
|
contents = node.get_text_contents()
|
||||||
|
|
||||||
|
# from <module> cimport ...
|
||||||
|
matches = pyx_from_import_re.findall(contents)
|
||||||
|
# cimport <module>
|
||||||
|
matches += pyx_import_re.findall(contents)
|
||||||
|
|
||||||
|
# Modules can be either .pxd or .pyx files
|
||||||
|
files = [m.replace('.', '/') + '.pxd' for m in matches]
|
||||||
|
files += [m.replace('.', '/') + '.pyx' for m in matches]
|
||||||
|
|
||||||
|
# cdef extern from <file>
|
||||||
|
files += cdef_import_re.findall(contents)
|
||||||
|
|
||||||
|
# Handle relative imports
|
||||||
|
cur_dir = str(node.get_dir())
|
||||||
|
files = [cur_dir + f if f.startswith('/') else f for f in files]
|
||||||
|
|
||||||
|
# Filter out non-existing files (probably system imports)
|
||||||
|
files = [f for f in files if env.File(f).exists()]
|
||||||
|
return env.File(files)
|
||||||
|
|
||||||
|
|
||||||
|
pyxscanner = Scanner(function=pyx_scan, skeys=['.pyx', '.pxd'], recursive=True)
|
||||||
|
cythonAction = Action("$CYTHONCOM")
|
||||||
|
|
||||||
|
|
||||||
|
def create_builder(env):
|
||||||
|
try:
|
||||||
|
cython = env['BUILDERS']['Cython']
|
||||||
|
except KeyError:
|
||||||
|
cython = SCons.Builder.Builder(
|
||||||
|
action=cythonAction,
|
||||||
|
emitter={},
|
||||||
|
suffix=cython_suffix_emitter,
|
||||||
|
single_source=1
|
||||||
|
)
|
||||||
|
env.Append(SCANNERS=pyxscanner)
|
||||||
|
env['BUILDERS']['Cython'] = cython
|
||||||
|
return cython
|
||||||
|
|
||||||
|
def cython_suffix_emitter(env, source):
|
||||||
|
return "$CYTHONCFILESUFFIX"
|
||||||
|
|
||||||
|
def generate(env):
|
||||||
|
env["CYTHON"] = "cythonize"
|
||||||
|
env["CYTHONCOM"] = "$CYTHON $CYTHONFLAGS $SOURCE"
|
||||||
|
env["CYTHONCFILESUFFIX"] = ".cpp"
|
||||||
|
|
||||||
|
c_file, _ = SCons.Tool.createCFileBuilders(env)
|
||||||
|
|
||||||
|
c_file.suffix['.pyx'] = cython_suffix_emitter
|
||||||
|
c_file.add_action('.pyx', cythonAction)
|
||||||
|
|
||||||
|
c_file.suffix['.py'] = cython_suffix_emitter
|
||||||
|
c_file.add_action('.py', cythonAction)
|
||||||
|
|
||||||
|
create_builder(env)
|
||||||
|
|
||||||
|
def exists(env):
|
||||||
|
return True
|
||||||
121
cereal/visionipc/ipc.cc
Normal file
121
cereal/visionipc/ipc.cc
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#define getsocket() socket(AF_UNIX, SOCK_STREAM, 0)
|
||||||
|
#else
|
||||||
|
#define getsocket() socket(AF_UNIX, SOCK_SEQPACKET, 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "cereal/visionipc/ipc.h"
|
||||||
|
|
||||||
|
int ipc_connect(const char* socket_path) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
int sock = getsocket();
|
||||||
|
|
||||||
|
if (sock < 0) return -1;
|
||||||
|
struct sockaddr_un addr = {
|
||||||
|
.sun_family = AF_UNIX,
|
||||||
|
};
|
||||||
|
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path);
|
||||||
|
err = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
|
||||||
|
if (err != 0) {
|
||||||
|
close(sock);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ipc_bind(const char* socket_path) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
unlink(socket_path);
|
||||||
|
|
||||||
|
int sock = getsocket();
|
||||||
|
|
||||||
|
struct sockaddr_un addr = {
|
||||||
|
.sun_family = AF_UNIX,
|
||||||
|
};
|
||||||
|
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path);
|
||||||
|
err = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
|
||||||
|
assert(err == 0);
|
||||||
|
|
||||||
|
err = listen(sock, 3);
|
||||||
|
assert(err == 0);
|
||||||
|
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int ipc_sendrecv_with_fds(bool send, int fd, void *buf, size_t buf_size, int* fds, int num_fds,
|
||||||
|
int *out_num_fds) {
|
||||||
|
char control_buf[CMSG_SPACE(sizeof(int) * num_fds)];
|
||||||
|
memset(control_buf, 0, CMSG_SPACE(sizeof(int) * num_fds));
|
||||||
|
|
||||||
|
struct iovec iov = {
|
||||||
|
.iov_base = buf,
|
||||||
|
.iov_len = buf_size,
|
||||||
|
};
|
||||||
|
struct msghdr msg = {
|
||||||
|
.msg_iov = &iov,
|
||||||
|
.msg_iovlen = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (num_fds > 0) {
|
||||||
|
assert(fds);
|
||||||
|
|
||||||
|
msg.msg_control = control_buf;
|
||||||
|
msg.msg_controllen = CMSG_SPACE(sizeof(int) * num_fds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (send) {
|
||||||
|
if (num_fds) {
|
||||||
|
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
||||||
|
assert(cmsg);
|
||||||
|
cmsg->cmsg_level = SOL_SOCKET;
|
||||||
|
cmsg->cmsg_type = SCM_RIGHTS;
|
||||||
|
cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds);
|
||||||
|
memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * num_fds);
|
||||||
|
}
|
||||||
|
return sendmsg(fd, &msg, 0);
|
||||||
|
} else {
|
||||||
|
int r = recvmsg(fd, &msg, 0);
|
||||||
|
if (r < 0) return r;
|
||||||
|
|
||||||
|
int recv_fds = 0;
|
||||||
|
if (msg.msg_controllen > 0) {
|
||||||
|
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
||||||
|
assert(cmsg);
|
||||||
|
assert(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS);
|
||||||
|
recv_fds = (cmsg->cmsg_len - CMSG_LEN(0));
|
||||||
|
assert(recv_fds > 0 && (recv_fds % sizeof(int)) == 0);
|
||||||
|
recv_fds /= sizeof(int);
|
||||||
|
|
||||||
|
assert(fds && recv_fds <= num_fds);
|
||||||
|
memcpy(fds, CMSG_DATA(cmsg), sizeof(int) * recv_fds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.msg_flags) {
|
||||||
|
for (int i=0; i<recv_fds; i++) {
|
||||||
|
close(fds[i]);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds) {
|
||||||
|
assert(out_num_fds);
|
||||||
|
*out_num_fds = recv_fds;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
cereal/visionipc/ipc.h
Normal file
7
cereal/visionipc/ipc.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
int ipc_connect(const char* socket_path);
|
||||||
|
int ipc_bind(const char* socket_path);
|
||||||
|
int ipc_sendrecv_with_fds(bool send, int fd, void *buf, size_t buf_size, int* fds, int num_fds,
|
||||||
|
int *out_num_fds);
|
||||||
2
cereal/visionipc/test_runner.cc
Normal file
2
cereal/visionipc/test_runner.cc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
0
cereal/visionipc/tests/__init__.py
Normal file
0
cereal/visionipc/tests/__init__.py
Normal file
99
cereal/visionipc/tests/test_visionipc.py
Normal file
99
cereal/visionipc/tests/test_visionipc.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import unittest
|
||||||
|
import numpy as np
|
||||||
|
from cereal.visionipc import VisionIpcServer, VisionIpcClient, VisionStreamType
|
||||||
|
|
||||||
|
def zmq_sleep(t=1):
|
||||||
|
if "ZMQ" in os.environ:
|
||||||
|
time.sleep(t)
|
||||||
|
|
||||||
|
|
||||||
|
class TestVisionIpc(unittest.TestCase):
|
||||||
|
|
||||||
|
def setup_vipc(self, name, *stream_types, num_buffers=1, rgb=False, width=100, height=100, conflate=False):
|
||||||
|
self.server = VisionIpcServer(name)
|
||||||
|
for stream_type in stream_types:
|
||||||
|
self.server.create_buffers(stream_type, num_buffers, rgb, width, height)
|
||||||
|
self.server.start_listener()
|
||||||
|
|
||||||
|
if len(stream_types):
|
||||||
|
self.client = VisionIpcClient(name, stream_types[0], conflate)
|
||||||
|
self.assertTrue(self.client.connect(True))
|
||||||
|
else:
|
||||||
|
self.client = None
|
||||||
|
|
||||||
|
zmq_sleep()
|
||||||
|
return self.server, self.client
|
||||||
|
|
||||||
|
def test_connect(self):
|
||||||
|
self.setup_vipc("camerad", VisionStreamType.VISION_STREAM_ROAD)
|
||||||
|
self.assertTrue(self.client.is_connected)
|
||||||
|
|
||||||
|
def test_available_streams(self):
|
||||||
|
for k in range(4):
|
||||||
|
stream_types = set(random.choices([x.value for x in VisionStreamType], k=k))
|
||||||
|
self.setup_vipc("camerad", *stream_types)
|
||||||
|
available_streams = VisionIpcClient.available_streams("camerad", True)
|
||||||
|
self.assertEqual(available_streams, stream_types)
|
||||||
|
|
||||||
|
def test_buffers(self):
|
||||||
|
width, height, num_buffers = 100, 200, 5
|
||||||
|
self.setup_vipc("camerad", VisionStreamType.VISION_STREAM_ROAD, num_buffers=num_buffers, width=width, height=height)
|
||||||
|
self.assertEqual(self.client.width, width)
|
||||||
|
self.assertEqual(self.client.height, height)
|
||||||
|
self.assertGreater(self.client.buffer_len, 0)
|
||||||
|
self.assertEqual(self.client.num_buffers, num_buffers)
|
||||||
|
|
||||||
|
def test_yuv_rgb(self):
|
||||||
|
_, client_yuv = self.setup_vipc("camerad", VisionStreamType.VISION_STREAM_ROAD, rgb=False)
|
||||||
|
_, client_rgb = self.setup_vipc("navd", VisionStreamType.VISION_STREAM_MAP, rgb=True)
|
||||||
|
self.assertTrue(client_rgb.rgb)
|
||||||
|
self.assertFalse(client_yuv.rgb)
|
||||||
|
|
||||||
|
def test_send_single_buffer(self):
|
||||||
|
self.setup_vipc("camerad", VisionStreamType.VISION_STREAM_ROAD)
|
||||||
|
|
||||||
|
buf = np.zeros(self.client.buffer_len, dtype=np.uint8)
|
||||||
|
buf.view('<i4')[0] = 1234
|
||||||
|
self.server.send(VisionStreamType.VISION_STREAM_ROAD, buf, frame_id=1337)
|
||||||
|
|
||||||
|
recv_buf = self.client.recv()
|
||||||
|
self.assertIsNot(recv_buf, None)
|
||||||
|
self.assertEqual(recv_buf.data.view('<i4')[0], 1234)
|
||||||
|
self.assertEqual(self.client.frame_id, 1337)
|
||||||
|
|
||||||
|
def test_no_conflate(self):
|
||||||
|
self.setup_vipc("camerad", VisionStreamType.VISION_STREAM_ROAD)
|
||||||
|
|
||||||
|
buf = np.zeros(self.client.buffer_len, dtype=np.uint8)
|
||||||
|
self.server.send(VisionStreamType.VISION_STREAM_ROAD, buf, frame_id=1)
|
||||||
|
self.server.send(VisionStreamType.VISION_STREAM_ROAD, buf, frame_id=2)
|
||||||
|
|
||||||
|
recv_buf = self.client.recv()
|
||||||
|
self.assertIsNot(recv_buf, None)
|
||||||
|
self.assertEqual(self.client.frame_id, 1)
|
||||||
|
|
||||||
|
recv_buf = self.client.recv()
|
||||||
|
self.assertIsNot(recv_buf, None)
|
||||||
|
self.assertEqual(self.client.frame_id, 2)
|
||||||
|
|
||||||
|
def test_conflate(self):
|
||||||
|
self.setup_vipc("camerad", VisionStreamType.VISION_STREAM_ROAD, conflate=True)
|
||||||
|
|
||||||
|
buf = np.zeros(self.client.buffer_len, dtype=np.uint8)
|
||||||
|
self.server.send(VisionStreamType.VISION_STREAM_ROAD, buf, frame_id=1)
|
||||||
|
self.server.send(VisionStreamType.VISION_STREAM_ROAD, buf, frame_id=2)
|
||||||
|
|
||||||
|
recv_buf = self.client.recv()
|
||||||
|
self.assertIsNot(recv_buf, None)
|
||||||
|
self.assertEqual(self.client.frame_id, 2)
|
||||||
|
|
||||||
|
recv_buf = self.client.recv()
|
||||||
|
self.assertIs(recv_buf, None)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
35
cereal/visionipc/visionbuf.cc
Normal file
35
cereal/visionipc/visionbuf.cc
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#include "cereal/visionipc/visionbuf.h"
|
||||||
|
|
||||||
|
#define ALIGN(x, align) (((x) + (align)-1) & ~((align)-1))
|
||||||
|
|
||||||
|
void visionbuf_compute_aligned_width_and_height(int width, int height, int *aligned_w, int *aligned_h) {
|
||||||
|
*aligned_w = width;
|
||||||
|
*aligned_h = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisionBuf::init_rgb(size_t init_width, size_t init_height, size_t init_stride) {
|
||||||
|
this->rgb = true;
|
||||||
|
this->width = init_width;
|
||||||
|
this->height = init_height;
|
||||||
|
this->stride = init_stride;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisionBuf::init_yuv(size_t init_width, size_t init_height, size_t init_stride, size_t init_uv_offset){
|
||||||
|
this->rgb = false;
|
||||||
|
this->width = init_width;
|
||||||
|
this->height = init_height;
|
||||||
|
this->stride = init_stride;
|
||||||
|
this->uv_offset = init_uv_offset;
|
||||||
|
|
||||||
|
this->y = (uint8_t *)this->addr;
|
||||||
|
this->uv = this->y + this->uv_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint64_t VisionBuf::get_frame_id() {
|
||||||
|
return *frame_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisionBuf::set_frame_id(uint64_t id) {
|
||||||
|
*frame_id = id;
|
||||||
|
}
|
||||||
66
cereal/visionipc/visionbuf.h
Normal file
66
cereal/visionipc/visionbuf.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cereal/visionipc/visionipc.h"
|
||||||
|
|
||||||
|
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <OpenCL/cl.h>
|
||||||
|
#else
|
||||||
|
#include <CL/cl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define VISIONBUF_SYNC_FROM_DEVICE 0
|
||||||
|
#define VISIONBUF_SYNC_TO_DEVICE 1
|
||||||
|
|
||||||
|
enum VisionStreamType {
|
||||||
|
VISION_STREAM_ROAD,
|
||||||
|
VISION_STREAM_DRIVER,
|
||||||
|
VISION_STREAM_WIDE_ROAD,
|
||||||
|
|
||||||
|
VISION_STREAM_MAP,
|
||||||
|
VISION_STREAM_MAX,
|
||||||
|
};
|
||||||
|
|
||||||
|
class VisionBuf {
|
||||||
|
public:
|
||||||
|
size_t len = 0;
|
||||||
|
size_t mmap_len = 0;
|
||||||
|
void * addr = nullptr;
|
||||||
|
uint64_t *frame_id;
|
||||||
|
int fd = 0;
|
||||||
|
|
||||||
|
bool rgb = false;
|
||||||
|
size_t width = 0;
|
||||||
|
size_t height = 0;
|
||||||
|
size_t stride = 0;
|
||||||
|
size_t uv_offset = 0;
|
||||||
|
|
||||||
|
// YUV
|
||||||
|
uint8_t * y = nullptr;
|
||||||
|
uint8_t * uv = nullptr;
|
||||||
|
|
||||||
|
// Visionipc
|
||||||
|
uint64_t server_id = 0;
|
||||||
|
size_t idx = 0;
|
||||||
|
VisionStreamType type;
|
||||||
|
|
||||||
|
// OpenCL
|
||||||
|
cl_mem buf_cl = nullptr;
|
||||||
|
cl_command_queue copy_q = nullptr;
|
||||||
|
|
||||||
|
// ion
|
||||||
|
int handle = 0;
|
||||||
|
|
||||||
|
void allocate(size_t len);
|
||||||
|
void import();
|
||||||
|
void init_cl(cl_device_id device_id, cl_context ctx);
|
||||||
|
void init_rgb(size_t width, size_t height, size_t stride);
|
||||||
|
void init_yuv(size_t width, size_t height, size_t stride, size_t uv_offset);
|
||||||
|
int sync(int dir);
|
||||||
|
int free();
|
||||||
|
|
||||||
|
void set_frame_id(uint64_t id);
|
||||||
|
uint64_t get_frame_id();
|
||||||
|
};
|
||||||
|
|
||||||
|
void visionbuf_compute_aligned_width_and_height(int width, int height, int *aligned_w, int *aligned_h);
|
||||||
94
cereal/visionipc/visionbuf_cl.cc
Normal file
94
cereal/visionipc/visionbuf_cl.cc
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#include "cereal/visionipc/visionbuf.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
std::atomic<int> offset = 0;
|
||||||
|
|
||||||
|
static void *malloc_with_fd(size_t len, int *fd) {
|
||||||
|
char full_path[0x100];
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
snprintf(full_path, sizeof(full_path)-1, "/tmp/visionbuf_%d_%d", getpid(), offset++);
|
||||||
|
#else
|
||||||
|
snprintf(full_path, sizeof(full_path)-1, "/dev/shm/visionbuf_%d_%d", getpid(), offset++);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
*fd = open(full_path, O_RDWR | O_CREAT, 0664);
|
||||||
|
assert(*fd >= 0);
|
||||||
|
|
||||||
|
unlink(full_path);
|
||||||
|
|
||||||
|
ftruncate(*fd, len);
|
||||||
|
void *addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0);
|
||||||
|
assert(addr != MAP_FAILED);
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisionBuf::allocate(size_t length) {
|
||||||
|
this->len = length;
|
||||||
|
this->mmap_len = this->len + sizeof(uint64_t);
|
||||||
|
this->addr = malloc_with_fd(this->mmap_len, &this->fd);
|
||||||
|
this->frame_id = (uint64_t*)((uint8_t*)this->addr + this->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisionBuf::init_cl(cl_device_id device_id, cl_context ctx){
|
||||||
|
int err;
|
||||||
|
|
||||||
|
this->copy_q = clCreateCommandQueue(ctx, device_id, 0, &err);
|
||||||
|
assert(err == 0);
|
||||||
|
|
||||||
|
this->buf_cl = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, this->len, this->addr, &err);
|
||||||
|
assert(err == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void VisionBuf::import(){
|
||||||
|
assert(this->fd >= 0);
|
||||||
|
this->addr = mmap(NULL, this->mmap_len, PROT_READ | PROT_WRITE, MAP_SHARED, this->fd, 0);
|
||||||
|
assert(this->addr != MAP_FAILED);
|
||||||
|
|
||||||
|
this->frame_id = (uint64_t*)((uint8_t*)this->addr + this->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int VisionBuf::sync(int dir) {
|
||||||
|
int err = 0;
|
||||||
|
if (!this->buf_cl) return 0;
|
||||||
|
|
||||||
|
if (dir == VISIONBUF_SYNC_FROM_DEVICE) {
|
||||||
|
err = clEnqueueReadBuffer(this->copy_q, this->buf_cl, CL_FALSE, 0, this->len, this->addr, 0, NULL, NULL);
|
||||||
|
} else {
|
||||||
|
err = clEnqueueWriteBuffer(this->copy_q, this->buf_cl, CL_FALSE, 0, this->len, this->addr, 0, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err == 0){
|
||||||
|
err = clFinish(this->copy_q);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int VisionBuf::free() {
|
||||||
|
int err = 0;
|
||||||
|
if (this->buf_cl){
|
||||||
|
err = clReleaseMemObject(this->buf_cl);
|
||||||
|
if (err != 0) return err;
|
||||||
|
|
||||||
|
err = clReleaseCommandQueue(this->copy_q);
|
||||||
|
if (err != 0) return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = munmap(this->addr, this->len);
|
||||||
|
if (err != 0) return err;
|
||||||
|
|
||||||
|
err = close(this->fd);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
161
cereal/visionipc/visionbuf_ion.cc
Normal file
161
cereal/visionipc/visionbuf_ion.cc
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <linux/ion.h>
|
||||||
|
#include <CL/cl_ext.h>
|
||||||
|
|
||||||
|
#include <msm_ion.h>
|
||||||
|
|
||||||
|
#include "cereal/visionipc/visionbuf.h"
|
||||||
|
|
||||||
|
// keep trying if x gets interrupted by a signal
|
||||||
|
#define HANDLE_EINTR(x) \
|
||||||
|
({ \
|
||||||
|
decltype(x) ret; \
|
||||||
|
int try_cnt = 0; \
|
||||||
|
do { \
|
||||||
|
ret = (x); \
|
||||||
|
} while (ret == -1 && errno == EINTR && try_cnt++ < 100); \
|
||||||
|
ret; \
|
||||||
|
})
|
||||||
|
|
||||||
|
// just hard-code these for convenience
|
||||||
|
// size_t device_page_size = 0;
|
||||||
|
// clGetDeviceInfo(device_id, CL_DEVICE_PAGE_SIZE_QCOM,
|
||||||
|
// sizeof(device_page_size), &device_page_size,
|
||||||
|
// NULL);
|
||||||
|
|
||||||
|
// size_t padding_cl = 0;
|
||||||
|
// clGetDeviceInfo(device_id, CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM,
|
||||||
|
// sizeof(padding_cl), &padding_cl,
|
||||||
|
// NULL);
|
||||||
|
#define DEVICE_PAGE_SIZE_CL 4096
|
||||||
|
#define PADDING_CL 0
|
||||||
|
|
||||||
|
struct IonFileHandle {
|
||||||
|
IonFileHandle() {
|
||||||
|
fd = open("/dev/ion", O_RDWR | O_NONBLOCK);
|
||||||
|
assert(fd >= 0);
|
||||||
|
}
|
||||||
|
~IonFileHandle() {
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
int fd = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
int ion_fd() {
|
||||||
|
static IonFileHandle fh;
|
||||||
|
return fh.fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisionBuf::allocate(size_t length) {
|
||||||
|
struct ion_allocation_data ion_alloc = {0};
|
||||||
|
ion_alloc.len = length + PADDING_CL + sizeof(uint64_t);
|
||||||
|
ion_alloc.align = 4096;
|
||||||
|
ion_alloc.heap_id_mask = 1 << ION_IOMMU_HEAP_ID;
|
||||||
|
ion_alloc.flags = ION_FLAG_CACHED;
|
||||||
|
|
||||||
|
int err = HANDLE_EINTR(ioctl(ion_fd(), ION_IOC_ALLOC, &ion_alloc));
|
||||||
|
assert(err == 0);
|
||||||
|
|
||||||
|
struct ion_fd_data ion_fd_data = {0};
|
||||||
|
ion_fd_data.handle = ion_alloc.handle;
|
||||||
|
err = HANDLE_EINTR(ioctl(ion_fd(), ION_IOC_SHARE, &ion_fd_data));
|
||||||
|
assert(err == 0);
|
||||||
|
|
||||||
|
void *mmap_addr = mmap(NULL, ion_alloc.len,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_SHARED, ion_fd_data.fd, 0);
|
||||||
|
assert(mmap_addr != MAP_FAILED);
|
||||||
|
|
||||||
|
memset(mmap_addr, 0, ion_alloc.len);
|
||||||
|
|
||||||
|
this->len = length;
|
||||||
|
this->mmap_len = ion_alloc.len;
|
||||||
|
this->addr = mmap_addr;
|
||||||
|
this->handle = ion_alloc.handle;
|
||||||
|
this->fd = ion_fd_data.fd;
|
||||||
|
this->frame_id = (uint64_t*)((uint8_t*)this->addr + this->len + PADDING_CL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisionBuf::import(){
|
||||||
|
int err;
|
||||||
|
assert(this->fd >= 0);
|
||||||
|
|
||||||
|
// Get handle
|
||||||
|
struct ion_fd_data fd_data = {0};
|
||||||
|
fd_data.fd = this->fd;
|
||||||
|
err = HANDLE_EINTR(ioctl(ion_fd(), ION_IOC_IMPORT, &fd_data));
|
||||||
|
assert(err == 0);
|
||||||
|
|
||||||
|
this->handle = fd_data.handle;
|
||||||
|
this->addr = mmap(NULL, this->mmap_len, PROT_READ | PROT_WRITE, MAP_SHARED, this->fd, 0);
|
||||||
|
assert(this->addr != MAP_FAILED);
|
||||||
|
|
||||||
|
this->frame_id = (uint64_t*)((uint8_t*)this->addr + this->len + PADDING_CL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisionBuf::init_cl(cl_device_id device_id, cl_context ctx) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
assert(((uintptr_t)this->addr % DEVICE_PAGE_SIZE_CL) == 0);
|
||||||
|
|
||||||
|
cl_mem_ion_host_ptr ion_cl = {0};
|
||||||
|
ion_cl.ext_host_ptr.allocation_type = CL_MEM_ION_HOST_PTR_QCOM;
|
||||||
|
ion_cl.ext_host_ptr.host_cache_policy = CL_MEM_HOST_UNCACHED_QCOM;
|
||||||
|
ion_cl.ion_filedesc = this->fd;
|
||||||
|
ion_cl.ion_hostptr = this->addr;
|
||||||
|
|
||||||
|
this->buf_cl = clCreateBuffer(ctx,
|
||||||
|
CL_MEM_USE_HOST_PTR | CL_MEM_EXT_HOST_PTR_QCOM,
|
||||||
|
this->len, &ion_cl, &err);
|
||||||
|
assert(err == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int VisionBuf::sync(int dir) {
|
||||||
|
struct ion_flush_data flush_data = {0};
|
||||||
|
flush_data.handle = this->handle;
|
||||||
|
flush_data.vaddr = this->addr;
|
||||||
|
flush_data.offset = 0;
|
||||||
|
flush_data.length = this->len;
|
||||||
|
|
||||||
|
// ION_IOC_INV_CACHES ~= DMA_FROM_DEVICE
|
||||||
|
// ION_IOC_CLEAN_CACHES ~= DMA_TO_DEVICE
|
||||||
|
// ION_IOC_CLEAN_INV_CACHES ~= DMA_BIDIRECTIONAL
|
||||||
|
|
||||||
|
struct ion_custom_data custom_data = {0};
|
||||||
|
|
||||||
|
assert(dir == VISIONBUF_SYNC_FROM_DEVICE || dir == VISIONBUF_SYNC_TO_DEVICE);
|
||||||
|
custom_data.cmd = (dir == VISIONBUF_SYNC_FROM_DEVICE) ?
|
||||||
|
ION_IOC_INV_CACHES : ION_IOC_CLEAN_CACHES;
|
||||||
|
|
||||||
|
custom_data.arg = (unsigned long)&flush_data;
|
||||||
|
return HANDLE_EINTR(ioctl(ion_fd(), ION_IOC_CUSTOM, &custom_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
int VisionBuf::free() {
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
if (this->buf_cl){
|
||||||
|
err = clReleaseMemObject(this->buf_cl);
|
||||||
|
if (err != 0) return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = munmap(this->addr, this->mmap_len);
|
||||||
|
if (err != 0) return err;
|
||||||
|
|
||||||
|
err = close(this->fd);
|
||||||
|
if (err != 0) return err;
|
||||||
|
|
||||||
|
struct ion_handle_data handle_data = {.handle = this->handle};
|
||||||
|
return HANDLE_EINTR(ioctl(ion_fd(), ION_IOC_FREE, &handle_data));
|
||||||
|
}
|
||||||
19
cereal/visionipc/visionipc.h
Normal file
19
cereal/visionipc/visionipc.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
constexpr int VISIONIPC_MAX_FDS = 128;
|
||||||
|
|
||||||
|
struct VisionIpcBufExtra {
|
||||||
|
uint32_t frame_id;
|
||||||
|
uint64_t timestamp_sof;
|
||||||
|
uint64_t timestamp_eof;
|
||||||
|
bool valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VisionIpcPacket {
|
||||||
|
uint64_t server_id;
|
||||||
|
size_t idx;
|
||||||
|
struct VisionIpcBufExtra extra;
|
||||||
|
};
|
||||||
143
cereal/visionipc/visionipc_client.cc
Normal file
143
cereal/visionipc/visionipc_client.cc
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "cereal/visionipc/ipc.h"
|
||||||
|
#include "cereal/visionipc/visionipc_client.h"
|
||||||
|
#include "cereal/visionipc/visionipc_server.h"
|
||||||
|
#include "cereal/logger/logger.h"
|
||||||
|
|
||||||
|
static int connect_to_vipc_server(const std::string &name, bool blocking) {
|
||||||
|
const std::string ipc_path = get_ipc_path(name);
|
||||||
|
int socket_fd = ipc_connect(ipc_path.c_str());
|
||||||
|
while (socket_fd < 0 && blocking) {
|
||||||
|
std::cout << "VisionIpcClient connecting" << std::endl;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
socket_fd = ipc_connect(ipc_path.c_str());
|
||||||
|
}
|
||||||
|
return socket_fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
VisionIpcClient::VisionIpcClient(std::string name, VisionStreamType type, bool conflate, cl_device_id device_id, cl_context ctx) : name(name), type(type), device_id(device_id), ctx(ctx) {
|
||||||
|
msg_ctx = Context::create();
|
||||||
|
sock = SubSocket::create(msg_ctx, get_endpoint_name(name, type), "127.0.0.1", conflate, false);
|
||||||
|
|
||||||
|
poller = Poller::create();
|
||||||
|
poller->registerSocket(sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect is not thread safe. Do not use the buffers while calling connect
|
||||||
|
bool VisionIpcClient::connect(bool blocking){
|
||||||
|
connected = false;
|
||||||
|
|
||||||
|
// Cleanup old buffers on reconnect
|
||||||
|
for (size_t i = 0; i < num_buffers; i++){
|
||||||
|
if (buffers[i].free() != 0) {
|
||||||
|
LOGE("Failed to free buffer %zu", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
num_buffers = 0;
|
||||||
|
|
||||||
|
int socket_fd = connect_to_vipc_server(name, blocking);
|
||||||
|
if (socket_fd < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Send stream type to server to request FDs
|
||||||
|
int r = ipc_sendrecv_with_fds(true, socket_fd, &type, sizeof(type), nullptr, 0, nullptr);
|
||||||
|
assert(r == sizeof(type));
|
||||||
|
|
||||||
|
// Get FDs
|
||||||
|
int fds[VISIONIPC_MAX_FDS];
|
||||||
|
VisionBuf bufs[VISIONIPC_MAX_FDS];
|
||||||
|
r = ipc_sendrecv_with_fds(false, socket_fd, &bufs, sizeof(bufs), fds, VISIONIPC_MAX_FDS, &num_buffers);
|
||||||
|
|
||||||
|
assert(num_buffers >= 0);
|
||||||
|
assert(r == sizeof(VisionBuf) * num_buffers);
|
||||||
|
|
||||||
|
// Import buffers
|
||||||
|
for (size_t i = 0; i < num_buffers; i++){
|
||||||
|
buffers[i] = bufs[i];
|
||||||
|
buffers[i].fd = fds[i];
|
||||||
|
buffers[i].import();
|
||||||
|
if (buffers[i].rgb) {
|
||||||
|
buffers[i].init_rgb(buffers[i].width, buffers[i].height, buffers[i].stride);
|
||||||
|
} else {
|
||||||
|
buffers[i].init_yuv(buffers[i].width, buffers[i].height, buffers[i].stride, buffers[i].uv_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device_id) buffers[i].init_cl(device_id, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(socket_fd);
|
||||||
|
connected = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
VisionBuf * VisionIpcClient::recv(VisionIpcBufExtra * extra, const int timeout_ms){
|
||||||
|
auto p = poller->poll(timeout_ms);
|
||||||
|
|
||||||
|
if (!p.size()){
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message * r = sock->receive(true);
|
||||||
|
if (r == nullptr){
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get buffer
|
||||||
|
assert(r->getSize() == sizeof(VisionIpcPacket));
|
||||||
|
VisionIpcPacket *packet = (VisionIpcPacket*)r->getData();
|
||||||
|
|
||||||
|
assert(packet->idx < num_buffers);
|
||||||
|
VisionBuf * buf = &buffers[packet->idx];
|
||||||
|
|
||||||
|
if (buf->server_id != packet->server_id){
|
||||||
|
connected = false;
|
||||||
|
delete r;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extra) {
|
||||||
|
*extra = packet->extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf->sync(VISIONBUF_SYNC_TO_DEVICE) != 0) {
|
||||||
|
LOGE("Failed to sync buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
delete r;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<VisionStreamType> VisionIpcClient::getAvailableStreams(const std::string &name, bool blocking) {
|
||||||
|
int socket_fd = connect_to_vipc_server(name, blocking);
|
||||||
|
if (socket_fd < 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
// Send VISION_STREAM_MAX to server to request available streams
|
||||||
|
int request = VISION_STREAM_MAX;
|
||||||
|
int r = ipc_sendrecv_with_fds(true, socket_fd, &request, sizeof(request), nullptr, 0, nullptr);
|
||||||
|
assert(r == sizeof(request));
|
||||||
|
|
||||||
|
VisionStreamType available_streams[VISION_STREAM_MAX] = {};
|
||||||
|
r = ipc_sendrecv_with_fds(false, socket_fd, &available_streams, sizeof(available_streams), nullptr, 0, nullptr);
|
||||||
|
assert((r >= 0) && (r % sizeof(VisionStreamType) == 0));
|
||||||
|
close(socket_fd);
|
||||||
|
return std::set<VisionStreamType>(available_streams, available_streams + r / sizeof(VisionStreamType));
|
||||||
|
}
|
||||||
|
|
||||||
|
VisionIpcClient::~VisionIpcClient(){
|
||||||
|
for (size_t i = 0; i < num_buffers; i++){
|
||||||
|
if (buffers[i].free() != 0) {
|
||||||
|
LOGE("Failed to free buffer %zu", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete sock;
|
||||||
|
delete poller;
|
||||||
|
delete msg_ctx;
|
||||||
|
}
|
||||||
30
cereal/visionipc/visionipc_client.h
Normal file
30
cereal/visionipc/visionipc_client.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "cereal/messaging/messaging.h"
|
||||||
|
#include "cereal/visionipc/visionbuf.h"
|
||||||
|
|
||||||
|
class VisionIpcClient {
|
||||||
|
private:
|
||||||
|
std::string name;
|
||||||
|
Context * msg_ctx;
|
||||||
|
SubSocket * sock;
|
||||||
|
Poller * poller;
|
||||||
|
|
||||||
|
cl_device_id device_id = nullptr;
|
||||||
|
cl_context ctx = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool connected = false;
|
||||||
|
VisionStreamType type;
|
||||||
|
int num_buffers = 0;
|
||||||
|
VisionBuf buffers[VISIONIPC_MAX_FDS];
|
||||||
|
VisionIpcClient(std::string name, VisionStreamType type, bool conflate, cl_device_id device_id=nullptr, cl_context ctx=nullptr);
|
||||||
|
~VisionIpcClient();
|
||||||
|
VisionBuf * recv(VisionIpcBufExtra * extra=nullptr, const int timeout_ms=100);
|
||||||
|
bool connect(bool blocking=true);
|
||||||
|
bool is_connected() { return connected; }
|
||||||
|
static std::set<VisionStreamType> getAvailableStreams(const std::string &name, bool blocking = true);
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
214
cereal/visionipc/visionipc_server.cc
Normal file
214
cereal/visionipc/visionipc_server.cc
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cassert>
|
||||||
|
#include <random>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "cereal/messaging/messaging.h"
|
||||||
|
#include "cereal/visionipc/ipc.h"
|
||||||
|
#include "cereal/visionipc/visionipc_server.h"
|
||||||
|
#include "cereal/logger/logger.h"
|
||||||
|
|
||||||
|
std::string get_endpoint_name(std::string name, VisionStreamType type){
|
||||||
|
if (messaging_use_zmq()){
|
||||||
|
assert(name == "camerad" || name == "navd");
|
||||||
|
return std::to_string(9000 + static_cast<int>(type));
|
||||||
|
} else {
|
||||||
|
return "visionipc_" + name + "_" + std::to_string(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_ipc_path(const std::string& name) {
|
||||||
|
std::string path = "/tmp/";
|
||||||
|
if (char* prefix = std::getenv("OPENPILOT_PREFIX")) {
|
||||||
|
path += std::string(prefix) + "_";
|
||||||
|
}
|
||||||
|
return path + "visionipc_" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
VisionIpcServer::VisionIpcServer(std::string name, cl_device_id device_id, cl_context ctx) : name(name), device_id(device_id), ctx(ctx) {
|
||||||
|
msg_ctx = Context::create();
|
||||||
|
|
||||||
|
std::random_device rd("/dev/urandom");
|
||||||
|
std::uniform_int_distribution<uint64_t> distribution(0, std::numeric_limits<uint64_t>::max());
|
||||||
|
server_id = distribution(rd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisionIpcServer::create_buffers(VisionStreamType type, size_t num_buffers, bool rgb, size_t width, size_t height){
|
||||||
|
// TODO: assert that this type is not created yet
|
||||||
|
assert(num_buffers < VISIONIPC_MAX_FDS);
|
||||||
|
int aligned_w = 0, aligned_h = 0;
|
||||||
|
|
||||||
|
size_t size = 0;
|
||||||
|
size_t stride = 0;
|
||||||
|
size_t uv_offset = 0;
|
||||||
|
|
||||||
|
if (rgb) {
|
||||||
|
visionbuf_compute_aligned_width_and_height(width, height, &aligned_w, &aligned_h);
|
||||||
|
size = (size_t)aligned_w * (size_t)aligned_h * 3;
|
||||||
|
stride = aligned_w * 3;
|
||||||
|
} else {
|
||||||
|
size = width * height * 3 / 2;
|
||||||
|
stride = width;
|
||||||
|
uv_offset = width * height;
|
||||||
|
}
|
||||||
|
|
||||||
|
create_buffers_with_sizes(type, num_buffers, rgb, width, height, size, stride, uv_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisionIpcServer::create_buffers_with_sizes(VisionStreamType type, size_t num_buffers, bool rgb, size_t width, size_t height, size_t size, size_t stride, size_t uv_offset) {
|
||||||
|
// Create map + alloc requested buffers
|
||||||
|
for (size_t i = 0; i < num_buffers; i++){
|
||||||
|
VisionBuf* buf = new VisionBuf();
|
||||||
|
buf->allocate(size);
|
||||||
|
buf->idx = i;
|
||||||
|
buf->type = type;
|
||||||
|
|
||||||
|
if (device_id) buf->init_cl(device_id, ctx);
|
||||||
|
|
||||||
|
rgb ? buf->init_rgb(width, height, stride) : buf->init_yuv(width, height, stride, uv_offset);
|
||||||
|
|
||||||
|
buffers[type].push_back(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_idx[type] = 0;
|
||||||
|
|
||||||
|
// Create msgq publisher for each of the `name` + type combos
|
||||||
|
// TODO: compute port number directly if using zmq
|
||||||
|
sockets[type] = PubSocket::create(msg_ctx, get_endpoint_name(name, type), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void VisionIpcServer::start_listener(){
|
||||||
|
listener_thread = std::thread(&VisionIpcServer::listener, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void VisionIpcServer::listener(){
|
||||||
|
std::cout << "Starting listener for: " << name << std::endl;
|
||||||
|
|
||||||
|
const std::string ipc_path = get_ipc_path(name);
|
||||||
|
int sock = ipc_bind(ipc_path.c_str());
|
||||||
|
assert(sock >= 0);
|
||||||
|
|
||||||
|
while (!should_exit){
|
||||||
|
// Wait for incoming connection
|
||||||
|
struct pollfd polls[1] = {{0}};
|
||||||
|
polls[0].fd = sock;
|
||||||
|
polls[0].events = POLLIN;
|
||||||
|
|
||||||
|
int ret = poll(polls, 1, 100);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EINTR || errno == EAGAIN) continue;
|
||||||
|
std::cout << "poll failed, stopping listener" << std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_exit) break;
|
||||||
|
if (!polls[0].revents) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle incoming request
|
||||||
|
int fd = accept(sock, NULL, NULL);
|
||||||
|
assert(fd >= 0);
|
||||||
|
|
||||||
|
VisionStreamType type = VisionStreamType::VISION_STREAM_MAX;
|
||||||
|
int r = ipc_sendrecv_with_fds(false, fd, &type, sizeof(type), nullptr, 0, nullptr);
|
||||||
|
assert(r == sizeof(type));
|
||||||
|
|
||||||
|
// send available stream types
|
||||||
|
if (type == VisionStreamType::VISION_STREAM_MAX) {
|
||||||
|
std::vector<VisionStreamType> available_stream_types;
|
||||||
|
for (auto& [stream_type, _] : buffers) {
|
||||||
|
available_stream_types.push_back(stream_type);
|
||||||
|
}
|
||||||
|
r = ipc_sendrecv_with_fds(true, fd, available_stream_types.data(), available_stream_types.size() * sizeof(VisionStreamType), nullptr, 0, nullptr);
|
||||||
|
assert(r == available_stream_types.size() * sizeof(VisionStreamType));
|
||||||
|
close(fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffers.count(type) <= 0) {
|
||||||
|
std::cout << "got request for invalid buffer type: " << type << std::endl;
|
||||||
|
close(fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fds[VISIONIPC_MAX_FDS];
|
||||||
|
int num_fds = buffers[type].size();
|
||||||
|
VisionBuf bufs[VISIONIPC_MAX_FDS];
|
||||||
|
|
||||||
|
for (int i = 0; i < num_fds; i++){
|
||||||
|
fds[i] = buffers[type][i]->fd;
|
||||||
|
bufs[i] = *buffers[type][i];
|
||||||
|
|
||||||
|
// Remove some private openCL/ion metadata
|
||||||
|
bufs[i].buf_cl = 0;
|
||||||
|
bufs[i].copy_q = 0;
|
||||||
|
bufs[i].handle = 0;
|
||||||
|
|
||||||
|
bufs[i].server_id = server_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = ipc_sendrecv_with_fds(true, fd, &bufs, sizeof(VisionBuf) * num_fds, fds, num_fds, nullptr);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Stopping listener for: " << name << std::endl;
|
||||||
|
close(sock);
|
||||||
|
unlink(ipc_path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
VisionBuf * VisionIpcServer::get_buffer(VisionStreamType type){
|
||||||
|
// Do we want to keep track if the buffer has been sent out yet and warn user?
|
||||||
|
assert(buffers.count(type));
|
||||||
|
auto b = buffers[type];
|
||||||
|
return b[cur_idx[type]++ % b.size()];
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisionIpcServer::send(VisionBuf * buf, VisionIpcBufExtra * extra, bool sync){
|
||||||
|
if (sync) {
|
||||||
|
if (buf->sync(VISIONBUF_SYNC_FROM_DEVICE) != 0) {
|
||||||
|
LOGE("Failed to sync buffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(buffers.count(buf->type));
|
||||||
|
assert(buf->idx < buffers[buf->type].size());
|
||||||
|
|
||||||
|
// Send over correct msgq socket
|
||||||
|
VisionIpcPacket packet = {0};
|
||||||
|
packet.server_id = server_id;
|
||||||
|
packet.idx = buf->idx;
|
||||||
|
packet.extra = *extra;
|
||||||
|
|
||||||
|
sockets[buf->type]->send((char*)&packet, sizeof(packet));
|
||||||
|
}
|
||||||
|
|
||||||
|
VisionIpcServer::~VisionIpcServer(){
|
||||||
|
should_exit = true;
|
||||||
|
listener_thread.join();
|
||||||
|
|
||||||
|
// VisionBuf cleanup
|
||||||
|
for (auto const& [type, buf] : buffers) {
|
||||||
|
for (VisionBuf* b : buf){
|
||||||
|
if (b->free() != 0) {
|
||||||
|
LOGE("Failed to free buffer");
|
||||||
|
}
|
||||||
|
delete b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Messaging cleanup
|
||||||
|
for (auto const& [type, sock] : sockets) {
|
||||||
|
delete sock;
|
||||||
|
}
|
||||||
|
delete msg_ctx;
|
||||||
|
}
|
||||||
42
cereal/visionipc/visionipc_server.h
Normal file
42
cereal/visionipc/visionipc_server.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "cereal/messaging/messaging.h"
|
||||||
|
#include "cereal/visionipc/visionbuf.h"
|
||||||
|
|
||||||
|
std::string get_endpoint_name(std::string name, VisionStreamType type);
|
||||||
|
std::string get_ipc_path(const std::string &name);
|
||||||
|
|
||||||
|
class VisionIpcServer {
|
||||||
|
private:
|
||||||
|
cl_device_id device_id = nullptr;
|
||||||
|
cl_context ctx = nullptr;
|
||||||
|
uint64_t server_id;
|
||||||
|
|
||||||
|
std::atomic<bool> should_exit = false;
|
||||||
|
std::string name;
|
||||||
|
std::thread listener_thread;
|
||||||
|
|
||||||
|
std::map<VisionStreamType, std::atomic<size_t> > cur_idx;
|
||||||
|
std::map<VisionStreamType, std::vector<VisionBuf*> > buffers;
|
||||||
|
|
||||||
|
Context * msg_ctx;
|
||||||
|
std::map<VisionStreamType, PubSocket*> sockets;
|
||||||
|
|
||||||
|
void listener(void);
|
||||||
|
|
||||||
|
public:
|
||||||
|
VisionIpcServer(std::string name, cl_device_id device_id=nullptr, cl_context ctx=nullptr);
|
||||||
|
~VisionIpcServer();
|
||||||
|
|
||||||
|
VisionBuf * get_buffer(VisionStreamType type);
|
||||||
|
|
||||||
|
void create_buffers(VisionStreamType type, size_t num_buffers, bool rgb, size_t width, size_t height);
|
||||||
|
void create_buffers_with_sizes(VisionStreamType type, size_t num_buffers, bool rgb, size_t width, size_t height, size_t size, size_t stride, size_t uv_offset);
|
||||||
|
void send(VisionBuf * buf, VisionIpcBufExtra * extra, bool sync=true);
|
||||||
|
void start_listener();
|
||||||
|
};
|
||||||
148
cereal/visionipc/visionipc_tests.cc
Normal file
148
cereal/visionipc/visionipc_tests.cc
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
#include "cereal/visionipc/visionipc_server.h"
|
||||||
|
#include "cereal/visionipc/visionipc_client.h"
|
||||||
|
|
||||||
|
static void zmq_sleep(int milliseconds=1000){
|
||||||
|
if (messaging_use_zmq()){
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Connecting"){
|
||||||
|
VisionIpcServer server("camerad");
|
||||||
|
server.create_buffers(VISION_STREAM_ROAD, 1, false, 100, 100);
|
||||||
|
server.start_listener();
|
||||||
|
|
||||||
|
VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_ROAD, false);
|
||||||
|
REQUIRE(client.connect());
|
||||||
|
|
||||||
|
REQUIRE(client.connected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("getAvailableStreams"){
|
||||||
|
VisionIpcServer server("camerad");
|
||||||
|
server.create_buffers(VISION_STREAM_ROAD, 1, false, 100, 100);
|
||||||
|
server.create_buffers(VISION_STREAM_WIDE_ROAD, 1, false, 100, 100);
|
||||||
|
server.start_listener();
|
||||||
|
auto available_streams = VisionIpcClient::getAvailableStreams("camerad");
|
||||||
|
REQUIRE(available_streams.size() == 2);
|
||||||
|
REQUIRE(available_streams.count(VISION_STREAM_ROAD) == 1);
|
||||||
|
REQUIRE(available_streams.count(VISION_STREAM_WIDE_ROAD) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Check buffers"){
|
||||||
|
size_t width = 100, height = 200, num_buffers = 5;
|
||||||
|
VisionIpcServer server("camerad");
|
||||||
|
server.create_buffers(VISION_STREAM_ROAD, num_buffers, false, width, height);
|
||||||
|
server.start_listener();
|
||||||
|
|
||||||
|
VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_ROAD, false);
|
||||||
|
REQUIRE(client.connect());
|
||||||
|
|
||||||
|
REQUIRE(client.buffers[0].width == width);
|
||||||
|
REQUIRE(client.buffers[0].height == height);
|
||||||
|
REQUIRE(client.buffers[0].len);
|
||||||
|
REQUIRE(client.num_buffers == num_buffers);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Check yuv/rgb"){
|
||||||
|
VisionIpcServer server("camerad");
|
||||||
|
server.create_buffers(VISION_STREAM_ROAD, 1, false, 100, 100);
|
||||||
|
server.create_buffers(VISION_STREAM_MAP, 1, true, 100, 100);
|
||||||
|
server.start_listener();
|
||||||
|
|
||||||
|
VisionIpcClient client_yuv = VisionIpcClient("camerad", VISION_STREAM_ROAD, false);
|
||||||
|
VisionIpcClient client_rgb = VisionIpcClient("camerad", VISION_STREAM_MAP, false);
|
||||||
|
client_yuv.connect();
|
||||||
|
client_rgb.connect();
|
||||||
|
|
||||||
|
REQUIRE(client_rgb.buffers[0].rgb == true);
|
||||||
|
REQUIRE(client_yuv.buffers[0].rgb == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Send single buffer"){
|
||||||
|
VisionIpcServer server("camerad");
|
||||||
|
server.create_buffers(VISION_STREAM_ROAD, 1, true, 100, 100);
|
||||||
|
server.start_listener();
|
||||||
|
|
||||||
|
VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_ROAD, false);
|
||||||
|
REQUIRE(client.connect());
|
||||||
|
zmq_sleep();
|
||||||
|
|
||||||
|
VisionBuf * buf = server.get_buffer(VISION_STREAM_ROAD);
|
||||||
|
REQUIRE(buf != nullptr);
|
||||||
|
|
||||||
|
*((uint64_t*)buf->addr) = 1234;
|
||||||
|
|
||||||
|
VisionIpcBufExtra extra = {0};
|
||||||
|
extra.frame_id = 1337;
|
||||||
|
buf->set_frame_id(extra.frame_id);
|
||||||
|
|
||||||
|
server.send(buf, &extra);
|
||||||
|
|
||||||
|
VisionIpcBufExtra extra_recv = {0};
|
||||||
|
VisionBuf * recv_buf = client.recv(&extra_recv);
|
||||||
|
REQUIRE(recv_buf != nullptr);
|
||||||
|
REQUIRE(*(uint64_t*)recv_buf->addr == 1234);
|
||||||
|
REQUIRE(extra_recv.frame_id == extra.frame_id);
|
||||||
|
REQUIRE(recv_buf->get_frame_id() == extra.frame_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("Test no conflate"){
|
||||||
|
VisionIpcServer server("camerad");
|
||||||
|
server.create_buffers(VISION_STREAM_ROAD, 1, true, 100, 100);
|
||||||
|
server.start_listener();
|
||||||
|
|
||||||
|
VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_ROAD, false);
|
||||||
|
REQUIRE(client.connect());
|
||||||
|
zmq_sleep();
|
||||||
|
|
||||||
|
VisionBuf * buf = server.get_buffer(VISION_STREAM_ROAD);
|
||||||
|
REQUIRE(buf != nullptr);
|
||||||
|
|
||||||
|
VisionIpcBufExtra extra = {0};
|
||||||
|
extra.frame_id = 1;
|
||||||
|
server.send(buf, &extra);
|
||||||
|
extra.frame_id = 2;
|
||||||
|
server.send(buf, &extra);
|
||||||
|
|
||||||
|
VisionIpcBufExtra extra_recv = {0};
|
||||||
|
VisionBuf * recv_buf = client.recv(&extra_recv);
|
||||||
|
REQUIRE(recv_buf != nullptr);
|
||||||
|
REQUIRE(extra_recv.frame_id == 1);
|
||||||
|
|
||||||
|
recv_buf = client.recv(&extra_recv);
|
||||||
|
REQUIRE(recv_buf != nullptr);
|
||||||
|
REQUIRE(extra_recv.frame_id == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Test conflate"){
|
||||||
|
VisionIpcServer server("camerad");
|
||||||
|
server.create_buffers(VISION_STREAM_ROAD, 1, true, 100, 100);
|
||||||
|
server.start_listener();
|
||||||
|
|
||||||
|
VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_ROAD, true);
|
||||||
|
REQUIRE(client.connect());
|
||||||
|
zmq_sleep();
|
||||||
|
|
||||||
|
VisionBuf * buf = server.get_buffer(VISION_STREAM_ROAD);
|
||||||
|
REQUIRE(buf != nullptr);
|
||||||
|
|
||||||
|
VisionIpcBufExtra extra = {0};
|
||||||
|
extra.frame_id = 1;
|
||||||
|
server.send(buf, &extra);
|
||||||
|
extra.frame_id = 2;
|
||||||
|
server.send(buf, &extra);
|
||||||
|
|
||||||
|
VisionIpcBufExtra extra_recv = {0};
|
||||||
|
VisionBuf * recv_buf = client.recv(&extra_recv);
|
||||||
|
REQUIRE(recv_buf != nullptr);
|
||||||
|
REQUIRE(extra_recv.frame_id == 2);
|
||||||
|
|
||||||
|
recv_buf = client.recv(&extra_recv);
|
||||||
|
REQUIRE(recv_buf == nullptr);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user