mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-27 03:02:05 +08:00
Merge remote-tracking branch 'refs/remotes/openpilot/master' into small-sync
# Conflicts: # cereal # opendbc # panda # selfdrive/car/__init__.py # selfdrive/monitoring/dmonitoringd.py
This commit is contained in:
@@ -37,8 +37,8 @@ jobs:
|
||||
run: |
|
||||
${{ env.RUN }} "pytest tools/plotjuggler/"
|
||||
|
||||
simulator:
|
||||
name: simulator
|
||||
simulator_build:
|
||||
name: simulator docker build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
timeout-minutes: 45
|
||||
@@ -56,6 +56,28 @@ jobs:
|
||||
run: |
|
||||
selfdrive/test/docker_build.sh sim
|
||||
|
||||
simulator_driving:
|
||||
name: simulator driving
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- run: git lfs pull
|
||||
- uses: ./.github/workflows/setup-with-retry
|
||||
- name: Build base docker image
|
||||
run: eval "$BUILD"
|
||||
- name: Build openpilot
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Run bridge test
|
||||
run: |
|
||||
${{ env.RUN }} "export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && \
|
||||
source selfdrive/test/setup_xvfb.sh && \
|
||||
source selfdrive/test/setup_vsound.sh && \
|
||||
CI=1 tools/sim/tests/test_metadrive_bridge.py"
|
||||
|
||||
devcontainer:
|
||||
name: devcontainer
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -33,7 +33,7 @@ repos:
|
||||
- -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints
|
||||
- --builtins clear,rare,informal,usage,code,names,en-GB_to_en-US
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.4.2
|
||||
rev: v0.4.3
|
||||
hooks:
|
||||
- id: ruff
|
||||
exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)'
|
||||
|
||||
Vendored
+80
-139
@@ -9,11 +9,6 @@ def retryWithDelay(int maxRetries, int delay, Closure body) {
|
||||
throw Exception("Failed after ${maxRetries} retries")
|
||||
}
|
||||
|
||||
// check if started by timer: https://gist.github.com/aaclarker/75b8a0eb2b4d600779f84f8e849f2c37
|
||||
def isJobStartedByTimer() {
|
||||
return currentBuild.getBuildCauses()[0]["shortDescription"].matches("Started by timer");
|
||||
}
|
||||
|
||||
def device(String ip, String step_label, String cmd) {
|
||||
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
|
||||
def ssh_cmd = """
|
||||
@@ -31,7 +26,6 @@ export SOURCE_DIR=${env.SOURCE_DIR}
|
||||
export GIT_BRANCH=${env.GIT_BRANCH}
|
||||
export GIT_COMMIT=${env.GIT_COMMIT}
|
||||
export AZURE_TOKEN='${env.AZURE_TOKEN}'
|
||||
export AZURE_TOKEN_OPENPILOT_RELEASES='${env.AZURE_TOKEN_OPENPILOT_RELEASES}'
|
||||
export MAPBOX_TOKEN='${env.MAPBOX_TOKEN}'
|
||||
# only use 1 thread for tici tests since most require HIL
|
||||
export PYTEST_ADDOPTS="-n 0"
|
||||
@@ -111,7 +105,7 @@ def pcStage(String stageName, Closure body) {
|
||||
|
||||
checkout scm
|
||||
|
||||
def dockerArgs = "--user=batman -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/scons_cache:/tmp/scons_cache -e PYTHONPATH=${env.WORKSPACE} -e AZURE_TOKEN_OPENPILOT_RELEASES='${env.AZURE_TOKEN_OPENPILOT_RELEASES}' --cpus=8 --memory 16g -e PYTEST_ADDOPTS='-n8'";
|
||||
def dockerArgs = "--user=batman -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/scons_cache:/tmp/scons_cache -e PYTHONPATH=${env.WORKSPACE} --cpus=8 --memory 16g -e PYTEST_ADDOPTS='-n8'";
|
||||
|
||||
def openpilot_base = retryWithDelay (3, 15) {
|
||||
return docker.build("openpilot-base:build-${env.GIT_COMMIT}", "-f Dockerfile.openpilot_base .")
|
||||
@@ -119,7 +113,7 @@ def pcStage(String stageName, Closure body) {
|
||||
|
||||
lock(resource: "", label: 'pc', inversePrecedence: true, quantity: 1) {
|
||||
openpilot_base.inside(dockerArgs) {
|
||||
timeout(time: 25, unit: 'MINUTES') {
|
||||
timeout(time: 20, unit: 'MINUTES') {
|
||||
try {
|
||||
retryWithDelay (3, 15) {
|
||||
sh "git config --global --add safe.directory '*'"
|
||||
@@ -141,56 +135,14 @@ def pcStage(String stageName, Closure body) {
|
||||
def setupCredentials() {
|
||||
withCredentials([
|
||||
string(credentialsId: 'azure_token', variable: 'AZURE_TOKEN'),
|
||||
string(credentialsId: 'azure_token_openpilot_releases', variable: 'AZURE_TOKEN_OPENPILOT_RELEASES'),
|
||||
string(credentialsId: 'mapbox_token', variable: 'MAPBOX_TOKEN')
|
||||
]) {
|
||||
env.AZURE_TOKEN = "${AZURE_TOKEN}"
|
||||
env.AZURE_TOKEN_OPENPILOT_RELEASES = "${AZURE_TOKEN_OPENPILOT_RELEASES}"
|
||||
env.MAPBOX_TOKEN = "${MAPBOX_TOKEN}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def build_git_release(String channel_name) {
|
||||
return parallel (
|
||||
"${channel_name} (git)": {
|
||||
deviceStage("build git", "tici-needs-can", [], [
|
||||
["build ${channel_name}", "RELEASE_BRANCH=${channel_name} $SOURCE_DIR/release/build_git_release.sh"],
|
||||
])
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def build_casync_release(String channel_name, def is_release) {
|
||||
def extra_env = is_release ? "RELEASE=1 " : ""
|
||||
def build_dir = "/data/openpilot"
|
||||
|
||||
extra_env += "TMPDIR=/data/tmp PYTHONPATH=$SOURCE_DIR"
|
||||
|
||||
return deviceStage("build casync", "tici-needs-can", [], [
|
||||
["build", "${extra_env} $SOURCE_DIR/release/build_release.sh ${build_dir}"],
|
||||
["package + upload", "${extra_env} $SOURCE_DIR/release/package_casync_build.py ${build_dir}"],
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
def build_stage() {
|
||||
return parallel (
|
||||
'nightly': {
|
||||
build_casync_release("nightly", true);
|
||||
},
|
||||
'master': {
|
||||
build_casync_release("master", false);
|
||||
},
|
||||
'publish agnos': {
|
||||
pcStage("publish agnos") {
|
||||
sh "PYTHONWARNINGS=ignore release/package_casync_agnos.py"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
node {
|
||||
env.CI = "1"
|
||||
env.PYTHONWARNINGS = "error"
|
||||
@@ -205,108 +157,97 @@ node {
|
||||
'testing-closet*', 'hotfix-*']
|
||||
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
|
||||
|
||||
def nightlyBranch = "master"
|
||||
|
||||
def props = [];
|
||||
|
||||
if (env.BRANCH_NAME == nightlyBranch) {
|
||||
props.add(pipelineTriggers([
|
||||
cron('0 9 * * *') // at 2am PST (9am UTC) every night
|
||||
]))
|
||||
if (env.BRANCH_NAME != 'master') {
|
||||
properties([
|
||||
disableConcurrentBuilds(abortPrevious: true)
|
||||
])
|
||||
}
|
||||
|
||||
if (env.BRANCH_NAME != "master") {
|
||||
props.add(disableConcurrentBuilds(abortPrevious: true))
|
||||
}
|
||||
|
||||
properties(props);
|
||||
|
||||
try {
|
||||
if (env.BRANCH_NAME == 'devel-staging') {
|
||||
build_git_release("release3-staging")
|
||||
deviceStage("build release3-staging", "tici-needs-can", [], [
|
||||
["build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"],
|
||||
])
|
||||
}
|
||||
|
||||
if (env.BRANCH_NAME == 'master-ci') {
|
||||
build_git_release("nightly")
|
||||
deviceStage("build nightly", "tici-needs-can", [], [
|
||||
["build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"],
|
||||
])
|
||||
}
|
||||
|
||||
if (!env.BRANCH_NAME.matches(excludeRegex)) {
|
||||
parallel (
|
||||
// tici tests
|
||||
'onroad tests': {
|
||||
deviceStage("onroad", "tici-needs-can", [], [
|
||||
// TODO: ideally, this test runs in master-ci, but it takes 5+m to build it
|
||||
//["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"],
|
||||
["build openpilot", "cd selfdrive/manager && ./build.py"],
|
||||
["check dirty", "release/check-dirty.sh"],
|
||||
["onroad tests", "pytest selfdrive/test/test_onroad.py -s"],
|
||||
["time to onroad", "pytest selfdrive/test/test_time_to_onroad.py"],
|
||||
])
|
||||
},
|
||||
'HW + Unit Tests': {
|
||||
deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"],
|
||||
["test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"],
|
||||
["test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"],
|
||||
["test pigeond", "pytest system/ubloxd/tests/test_pigeond.py"],
|
||||
["test manager", "pytest selfdrive/manager/test/test_manager.py"],
|
||||
])
|
||||
},
|
||||
'loopback': {
|
||||
deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
|
||||
["build openpilot", "cd selfdrive/manager && ./build.py"],
|
||||
["test boardd loopback", "pytest selfdrive/boardd/tests/test_boardd_loopback.py"],
|
||||
])
|
||||
},
|
||||
'camerad': {
|
||||
deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["test camerad", "pytest system/camerad/test/test_camerad.py"],
|
||||
["test exposure", "pytest system/camerad/test/test_exposure.py"],
|
||||
])
|
||||
deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["test camerad", "pytest system/camerad/test/test_camerad.py"],
|
||||
["test exposure", "pytest system/camerad/test/test_exposure.py"],
|
||||
])
|
||||
},
|
||||
'sensord': {
|
||||
deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["test sensord", "pytest system/sensord/tests/test_sensord.py"],
|
||||
])
|
||||
deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["test sensord", "pytest system/sensord/tests/test_sensord.py"],
|
||||
])
|
||||
},
|
||||
'replay': {
|
||||
deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["model replay", "selfdrive/test/process_replay/model_replay.py"],
|
||||
])
|
||||
},
|
||||
'tizi': {
|
||||
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
||||
["build openpilot", "cd selfdrive/manager && ./build.py"],
|
||||
["test boardd loopback", "SINGLE_PANDA=1 pytest selfdrive/boardd/tests/test_boardd_loopback.py"],
|
||||
["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"],
|
||||
["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"],
|
||||
["test hw", "pytest system/hardware/tici/tests/test_hardware.py"],
|
||||
["test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py"],
|
||||
])
|
||||
},
|
||||
)
|
||||
}
|
||||
parallel (
|
||||
// tici tests
|
||||
'onroad tests': {
|
||||
deviceStage("onroad", "tici-needs-can", [], [
|
||||
// TODO: ideally, this test runs in master-ci, but it takes 5+m to build it
|
||||
//["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"],
|
||||
["build openpilot", "cd selfdrive/manager && ./build.py"],
|
||||
["check dirty", "release/check-dirty.sh"],
|
||||
["onroad tests", "pytest selfdrive/test/test_onroad.py -s"],
|
||||
["time to onroad", "pytest selfdrive/test/test_time_to_onroad.py"],
|
||||
])
|
||||
},
|
||||
'HW + Unit Tests': {
|
||||
deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"],
|
||||
["test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"],
|
||||
["test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"],
|
||||
["test pigeond", "pytest system/ubloxd/tests/test_pigeond.py"],
|
||||
["test manager", "pytest selfdrive/manager/test/test_manager.py"],
|
||||
])
|
||||
},
|
||||
'loopback': {
|
||||
deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
|
||||
["build openpilot", "cd selfdrive/manager && ./build.py"],
|
||||
["test boardd loopback", "pytest selfdrive/boardd/tests/test_boardd_loopback.py"],
|
||||
])
|
||||
},
|
||||
'camerad': {
|
||||
deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["test camerad", "pytest system/camerad/test/test_camerad.py"],
|
||||
["test exposure", "pytest system/camerad/test/test_exposure.py"],
|
||||
])
|
||||
deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["test camerad", "pytest system/camerad/test/test_camerad.py"],
|
||||
["test exposure", "pytest system/camerad/test/test_exposure.py"],
|
||||
])
|
||||
},
|
||||
'sensord': {
|
||||
deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["test sensord", "pytest system/sensord/tests/test_sensord.py"],
|
||||
])
|
||||
deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["test sensord", "pytest system/sensord/tests/test_sensord.py"],
|
||||
])
|
||||
},
|
||||
'replay': {
|
||||
deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["model replay", "selfdrive/test/process_replay/model_replay.py"],
|
||||
])
|
||||
},
|
||||
'tizi': {
|
||||
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
||||
["build openpilot", "cd selfdrive/manager && ./build.py"],
|
||||
["test boardd loopback", "SINGLE_PANDA=1 pytest selfdrive/boardd/tests/test_boardd_loopback.py"],
|
||||
["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"],
|
||||
["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"],
|
||||
["test hw", "pytest system/hardware/tici/tests/test_hardware.py"],
|
||||
["test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py"],
|
||||
])
|
||||
},
|
||||
|
||||
if (env.BRANCH_NAME == nightlyBranch && isJobStartedByTimer()) {
|
||||
stage('build release') {
|
||||
build_stage()
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch (Exception e) {
|
||||
currentBuild.result = 'FAILED'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
Submodule body updated: 864c5449ef...0e74db67ae
@@ -172,7 +172,6 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"Offroad_CarUnrecognized", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
|
||||
{"Offroad_ConnectivityNeeded", CLEAR_ON_MANAGER_START},
|
||||
{"Offroad_ConnectivityNeededPrompt", CLEAR_ON_MANAGER_START},
|
||||
{"Offroad_InvalidTime", CLEAR_ON_MANAGER_START},
|
||||
{"Offroad_IsTakingSnapshot", CLEAR_ON_MANAGER_START},
|
||||
{"Offroad_NeosUpdate", CLEAR_ON_MANAGER_START},
|
||||
{"Offroad_NoFirmware", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
|
||||
|
||||
@@ -246,12 +246,6 @@ std::string random_string(std::string::size_type length) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string dir_name(std::string const &path) {
|
||||
size_t pos = path.find_last_of("/");
|
||||
if (pos == std::string::npos) return "";
|
||||
return path.substr(0, pos);
|
||||
}
|
||||
|
||||
bool starts_with(const std::string &s1, const std::string &s2) {
|
||||
return strncmp(s1.c_str(), s2.c_str(), s2.size()) == 0;
|
||||
}
|
||||
@@ -277,20 +271,4 @@ std::string check_output(const std::string& command) {
|
||||
return result;
|
||||
}
|
||||
|
||||
struct tm get_time() {
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
|
||||
struct tm sys_time;
|
||||
gmtime_r(&rawtime, &sys_time);
|
||||
|
||||
return sys_time;
|
||||
}
|
||||
|
||||
bool time_valid(struct tm sys_time) {
|
||||
int year = 1900 + sys_time.tm_year;
|
||||
int month = 1 + sys_time.tm_mon;
|
||||
return (year > 2023) || (year == 2023 && month >= 6);
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <ctime>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -45,10 +44,6 @@ int set_realtime_priority(int level);
|
||||
int set_core_affinity(std::vector<int> cores);
|
||||
int set_file_descriptor_limit(uint64_t limit);
|
||||
|
||||
// ***** Time helpers *****
|
||||
struct tm get_time();
|
||||
bool time_valid(struct tm sys_time);
|
||||
|
||||
// ***** math helpers *****
|
||||
|
||||
// map x from [a1, a2] to [b1, b2]
|
||||
@@ -75,7 +70,6 @@ int getenv(const char* key, int default_val);
|
||||
float getenv(const char* key, float default_val);
|
||||
|
||||
std::string hexdump(const uint8_t* in, const size_t size);
|
||||
std::string dir_name(std::string const& path);
|
||||
bool starts_with(const std::string &s1, const std::string &s2);
|
||||
bool ends_with(const std::string &s, const std::string &suffix);
|
||||
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
|
||||
|
||||
# 285 Supported Cars
|
||||
# 286 Supported Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -29,6 +29,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Chrysler|Pacifica Hybrid 2018|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Chrysler&model=Pacifica Hybrid 2018">Buy Here</a></sub></details>||
|
||||
|Chrysler|Pacifica Hybrid 2019-23|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Chrysler&model=Pacifica Hybrid 2019-23">Buy Here</a></sub></details>||
|
||||
|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None||
|
||||
|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=CUPRA&model=Ateca 2018-23">Buy Here</a></sub></details>||
|
||||
|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Dodge&model=Durango 2020-21">Buy Here</a></sub></details>||
|
||||
|Ford|Bronco Sport 2021-23|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Bronco Sport 2021-23">Buy Here</a></sub></details>||
|
||||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape 2020-22">Buy Here</a></sub></details>||
|
||||
@@ -192,7 +193,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Nissan&model=Rogue 2018-20">Buy Here</a></sub></details>||
|
||||
|Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Nissan&model=X-Trail 2017">Buy Here</a></sub></details>||
|
||||
|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Ram connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ram&model=1500 2019-24">Buy Here</a></sub></details>||
|
||||
|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=SEAT&model=Ateca 2018">Buy Here</a></sub></details>||
|
||||
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=SEAT&model=Ateca 2016-23">Buy Here</a></sub></details>||
|
||||
|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=SEAT&model=Leon 2014-20">Buy Here</a></sub></details>||
|
||||
|Subaru|Ascent 2019-21|All[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Subaru&model=Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>||
|
||||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>6</sup>](#footnotes)|openpilot available[<sup>1,7</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Subaru&model=Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
|
||||
export VECLIB_MAXIMUM_THREADS=1
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="10"
|
||||
export AGNOS_VERSION="10.1"
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
Generated
+220
-218
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
@@ -311,13 +311,13 @@ aio = ["azure-core[aio] (>=1.28.0,<2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
version = "2.14.0"
|
||||
version = "2.15.0"
|
||||
description = "Internationalization utilities"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"},
|
||||
{file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"},
|
||||
{file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"},
|
||||
{file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -756,63 +756,63 @@ test = ["pytest", "pytest-timeout"]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.5.0"
|
||||
version = "7.5.1"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "coverage-7.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:432949a32c3e3f820af808db1833d6d1631664d53dd3ce487aa25d574e18ad1c"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2bd7065249703cbeb6d4ce679c734bef0ee69baa7bff9724361ada04a15b7e3b"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbfe6389c5522b99768a93d89aca52ef92310a96b99782973b9d11e80511f932"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39793731182c4be939b4be0cdecde074b833f6171313cf53481f869937129ed3"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a5dbe1ba1bf38d6c63b6d2c42132d45cbee6d9f0c51b52c59aa4afba057517"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:357754dcdfd811462a725e7501a9b4556388e8ecf66e79df6f4b988fa3d0b39a"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a81eb64feded34f40c8986869a2f764f0fe2db58c0530d3a4afbcde50f314880"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:51431d0abbed3a868e967f8257c5faf283d41ec882f58413cf295a389bb22e58"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-win32.whl", hash = "sha256:f609ebcb0242d84b7adeee2b06c11a2ddaec5464d21888b2c8255f5fd6a98ae4"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:6782cd6216fab5a83216cc39f13ebe30adfac2fa72688c5a4d8d180cd52e8f6a"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e768d870801f68c74c2b669fc909839660180c366501d4cc4b87efd6b0eee375"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:84921b10aeb2dd453247fd10de22907984eaf80901b578a5cf0bb1e279a587cb"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710c62b6e35a9a766b99b15cdc56d5aeda0914edae8bb467e9c355f75d14ee95"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c379cdd3efc0658e652a14112d51a7668f6bfca7445c5a10dee7eabecabba19d"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea9d3ca80bcf17edb2c08a4704259dadac196fe5e9274067e7a20511fad1743"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:41327143c5b1d715f5f98a397608f90ab9ebba606ae4e6f3389c2145410c52b1"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:565b2e82d0968c977e0b0f7cbf25fd06d78d4856289abc79694c8edcce6eb2de"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf3539007202ebfe03923128fedfdd245db5860a36810136ad95a564a2fdffff"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-win32.whl", hash = "sha256:bf0b4b8d9caa8d64df838e0f8dcf68fb570c5733b726d1494b87f3da85db3a2d"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c6384cc90e37cfb60435bbbe0488444e54b98700f727f16f64d8bfda0b84656"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fed7a72d54bd52f4aeb6c6e951f363903bd7d70bc1cad64dd1f087980d309ab9"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cbe6581fcff7c8e262eb574244f81f5faaea539e712a058e6707a9d272fe5b64"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad97ec0da94b378e593ef532b980c15e377df9b9608c7c6da3506953182398af"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd4bacd62aa2f1a1627352fe68885d6ee694bdaebb16038b6e680f2924a9b2cc"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf032b6c105881f9d77fa17d9eebe0ad1f9bfb2ad25777811f97c5362aa07f2"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ba01d9ba112b55bfa4b24808ec431197bb34f09f66f7cb4fd0258ff9d3711b1"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f0bfe42523893c188e9616d853c47685e1c575fe25f737adf473d0405dcfa7eb"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a9a7ef30a1b02547c1b23fa9a5564f03c9982fc71eb2ecb7f98c96d7a0db5cf2"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-win32.whl", hash = "sha256:3c2b77f295edb9fcdb6a250f83e6481c679335ca7e6e4a955e4290350f2d22a4"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:427e1e627b0963ac02d7c8730ca6d935df10280d230508c0ba059505e9233475"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dd88fce54abbdbf4c42fb1fea0e498973d07816f24c0e27a1ecaf91883ce69e"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a898c11dca8f8c97b467138004a30133974aacd572818c383596f8d5b2eb04a9"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07dfdd492d645eea1bd70fb1d6febdcf47db178b0d99161d8e4eed18e7f62fe7"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3d117890b6eee85887b1eed41eefe2e598ad6e40523d9f94c4c4b213258e4a4"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6afd2e84e7da40fe23ca588379f815fb6dbbb1b757c883935ed11647205111cb"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a9960dd1891b2ddf13a7fe45339cd59ecee3abb6b8326d8b932d0c5da208104f"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ced268e82af993d7801a9db2dbc1d2322e786c5dc76295d8e89473d46c6b84d4"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7c211f25777746d468d76f11719e64acb40eed410d81c26cefac641975beb88"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-win32.whl", hash = "sha256:262fffc1f6c1a26125d5d573e1ec379285a3723363f3bd9c83923c9593a2ac25"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:eed462b4541c540d63ab57b3fc69e7d8c84d5957668854ee4e408b50e92ce26a"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0194d654e360b3e6cc9b774e83235bae6b9b2cac3be09040880bb0e8a88f4a1"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33c020d3322662e74bc507fb11488773a96894aa82a622c35a5a28673c0c26f5"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbdf2cae14a06827bec50bd58e49249452d211d9caddd8bd80e35b53cb04631"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3235d7c781232e525b0761730e052388a01548bd7f67d0067a253887c6e8df46"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2de4e546f0ec4b2787d625e0b16b78e99c3e21bc1722b4977c0dddf11ca84e"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0e206259b73af35c4ec1319fd04003776e11e859936658cb6ceffdeba0f5be"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2055c4fb9a6ff624253d432aa471a37202cd8f458c033d6d989be4499aed037b"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075299460948cd12722a970c7eae43d25d37989da682997687b34ae6b87c0ef0"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-win32.whl", hash = "sha256:280132aada3bc2f0fac939a5771db4fbb84f245cb35b94fae4994d4c1f80dae7"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:c58536f6892559e030e6924896a44098bc1290663ea12532c78cef71d0df8493"},
|
||||
{file = "coverage-7.5.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:2b57780b51084d5223eee7b59f0d4911c31c16ee5aa12737c7a02455829ff067"},
|
||||
{file = "coverage-7.5.0.tar.gz", hash = "sha256:cf62d17310f34084c59c01e027259076479128d11e4661bb6c9acb38c5e19bb8"},
|
||||
{file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"},
|
||||
{file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"},
|
||||
{file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"},
|
||||
{file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"},
|
||||
{file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"},
|
||||
{file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"},
|
||||
{file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"},
|
||||
{file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"},
|
||||
{file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"},
|
||||
{file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"},
|
||||
{file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"},
|
||||
{file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"},
|
||||
{file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"},
|
||||
{file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"},
|
||||
{file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"},
|
||||
{file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"},
|
||||
{file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"},
|
||||
{file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"},
|
||||
{file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"},
|
||||
{file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"},
|
||||
{file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"},
|
||||
{file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"},
|
||||
{file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"},
|
||||
{file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"},
|
||||
{file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"},
|
||||
{file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"},
|
||||
{file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"},
|
||||
{file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"},
|
||||
{file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"},
|
||||
{file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"},
|
||||
{file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"},
|
||||
{file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"},
|
||||
{file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"},
|
||||
{file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"},
|
||||
{file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"},
|
||||
{file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"},
|
||||
{file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"},
|
||||
{file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"},
|
||||
{file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"},
|
||||
{file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"},
|
||||
{file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"},
|
||||
{file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"},
|
||||
{file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"},
|
||||
{file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"},
|
||||
{file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"},
|
||||
{file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"},
|
||||
{file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"},
|
||||
{file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"},
|
||||
{file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"},
|
||||
{file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"},
|
||||
{file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"},
|
||||
{file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -830,43 +830,43 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "42.0.5"
|
||||
version = "42.0.6"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"},
|
||||
{file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"},
|
||||
{file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"},
|
||||
{file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"},
|
||||
{file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"},
|
||||
{file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"},
|
||||
{file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"},
|
||||
{file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"},
|
||||
{file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"},
|
||||
{file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"},
|
||||
{file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"},
|
||||
{file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"},
|
||||
{file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"},
|
||||
{file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"},
|
||||
{file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"},
|
||||
{file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"},
|
||||
{file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"},
|
||||
{file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"},
|
||||
{file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"},
|
||||
{file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"},
|
||||
{file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"},
|
||||
{file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"},
|
||||
{file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"},
|
||||
{file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"},
|
||||
{file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"},
|
||||
{file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"},
|
||||
{file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"},
|
||||
{file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"},
|
||||
{file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"},
|
||||
{file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"},
|
||||
{file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"},
|
||||
{file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"},
|
||||
{file = "cryptography-42.0.6-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:073104df012fc815eed976cd7d0a386c8725d0d0947cf9c37f6c36a6c20feb1b"},
|
||||
{file = "cryptography-42.0.6-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:5967e3632f42b0c0f9dc2c9da88c79eabdda317860b246d1fbbde4a8bbbc3b44"},
|
||||
{file = "cryptography-42.0.6-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99831397fdc6e6e0aa088b060c278c6e635d25c0d4d14bdf045bf81792fda0a"},
|
||||
{file = "cryptography-42.0.6-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:089aeb297ff89615934b22c7631448598495ffd775b7d540a55cfee35a677bf4"},
|
||||
{file = "cryptography-42.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:97eeacae9aa526ddafe68b9202a535f581e21d78f16688a84c8dcc063618e121"},
|
||||
{file = "cryptography-42.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f4cece02478d73dacd52be57a521d168af64ae03d2a567c0c4eb6f189c3b9d79"},
|
||||
{file = "cryptography-42.0.6-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb6f56b004e898df5530fa873e598ec78eb338ba35f6fa1449970800b1d97c2"},
|
||||
{file = "cryptography-42.0.6-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:8b90c57b3cd6128e0863b894ce77bd36fcb5f430bf2377bc3678c2f56e232316"},
|
||||
{file = "cryptography-42.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d16a310c770cc49908c500c2ceb011f2840674101a587d39fa3ea828915b7e83"},
|
||||
{file = "cryptography-42.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e3442601d276bd9e961d618b799761b4e5d892f938e8a4fe1efbe2752be90455"},
|
||||
{file = "cryptography-42.0.6-cp37-abi3-win32.whl", hash = "sha256:00c0faa5b021457848d031ecff041262211cc1e2bce5f6e6e6c8108018f6b44a"},
|
||||
{file = "cryptography-42.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:b16b90605c62bcb3aa7755d62cf5e746828cfc3f965a65211849e00c46f8348d"},
|
||||
{file = "cryptography-42.0.6-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:eecca86813c6a923cabff284b82ff4d73d9e91241dc176250192c3a9b9902a54"},
|
||||
{file = "cryptography-42.0.6-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d93080d2b01b292e7ee4d247bf93ed802b0100f5baa3fa5fd6d374716fa480d4"},
|
||||
{file = "cryptography-42.0.6-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ff75b88a4d273c06d968ad535e6cb6a039dd32db54fe36f05ed62ac3ef64a44"},
|
||||
{file = "cryptography-42.0.6-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c05230d8aaaa6b8ab3ab41394dc06eb3d916131df1c9dcb4c94e8f041f704b74"},
|
||||
{file = "cryptography-42.0.6-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9184aff0856261ecb566a3eb26a05dfe13a292c85ce5c59b04e4aa09e5814187"},
|
||||
{file = "cryptography-42.0.6-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:4bdb39ecbf05626e4bfa1efd773bb10346af297af14fb3f4c7cb91a1d2f34a46"},
|
||||
{file = "cryptography-42.0.6-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e85f433230add2aa26b66d018e21134000067d210c9c68ef7544ba65fc52e3eb"},
|
||||
{file = "cryptography-42.0.6-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:65d529c31bd65d54ce6b926a01e1b66eacf770b7e87c0622516a840e400ec732"},
|
||||
{file = "cryptography-42.0.6-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f1e933b238978ccfa77b1fee0a297b3c04983f4cb84ae1c33b0ea4ae08266cc9"},
|
||||
{file = "cryptography-42.0.6-cp39-abi3-win32.whl", hash = "sha256:bc954251edcd8a952eeaec8ae989fec7fe48109ab343138d537b7ea5bb41071a"},
|
||||
{file = "cryptography-42.0.6-cp39-abi3-win_amd64.whl", hash = "sha256:9f1a3bc2747166b0643b00e0b56cd9b661afc9d5ff963acaac7a9c7b2b1ef638"},
|
||||
{file = "cryptography-42.0.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:945a43ebf036dd4b43ebfbbd6b0f2db29ad3d39df824fb77476ca5777a9dde33"},
|
||||
{file = "cryptography-42.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f567a82b7c2b99257cca2a1c902c1b129787278ff67148f188784245c7ed5495"},
|
||||
{file = "cryptography-42.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3b750279f3e7715df6f68050707a0cee7cbe81ba2eeb2f21d081bd205885ffed"},
|
||||
{file = "cryptography-42.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6981acac509cc9415344cb5bfea8130096ea6ebcc917e75503143a1e9e829160"},
|
||||
{file = "cryptography-42.0.6-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:076c92b08dd1ab88108bc84545187e10d3693a9299c593f98c4ea195a0b0ead7"},
|
||||
{file = "cryptography-42.0.6-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81dbe47e28b703bc4711ac74a64ef8b758a0cf056ce81d08e39116ab4bc126fa"},
|
||||
{file = "cryptography-42.0.6-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e1f5f15c5ddadf6ee4d1d624a2ae940f14bd74536230b0056ccb28bb6248e42a"},
|
||||
{file = "cryptography-42.0.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:43e521f21c2458038d72e8cdfd4d4d9f1d00906a7b6636c4272e35f650d1699b"},
|
||||
{file = "cryptography-42.0.6.tar.gz", hash = "sha256:f987a244dfb0333fbd74a691c36000a2569eaf7c7cc2ac838f85f59f0588ddc9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1707,13 +1707,13 @@ testing = ["pytest (==7.1.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.3"
|
||||
version = "3.1.4"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
|
||||
{file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
|
||||
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
||||
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2051,7 +2051,6 @@ files = [
|
||||
{file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:9e2addd2d1866fe112bc6f80117bcc6bc25191c5ed1bfbcf9f1386a884252ae8"},
|
||||
{file = "lxml-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:f51969bac61441fd31f028d7b3b45962f3ecebf691a510495e5d2cd8c8092dbd"},
|
||||
{file = "lxml-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:b0b58fbfa1bf7367dde8a557994e3b1637294be6cf2169810375caf8571a085c"},
|
||||
{file = "lxml-5.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3e183c6e3298a2ed5af9d7a356ea823bccaab4ec2349dc9ed83999fd289d14d5"},
|
||||
{file = "lxml-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:804f74efe22b6a227306dd890eecc4f8c59ff25ca35f1f14e7482bbce96ef10b"},
|
||||
{file = "lxml-5.2.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08802f0c56ed150cc6885ae0788a321b73505d2263ee56dad84d200cab11c07a"},
|
||||
{file = "lxml-5.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f8c09ed18ecb4ebf23e02b8e7a22a05d6411911e6fabef3a36e4f371f4f2585"},
|
||||
@@ -3543,17 +3542,16 @@ pyrect = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.17.2"
|
||||
version = "2.18.0"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
|
||||
{file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
|
||||
{file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
|
||||
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
plugins = ["importlib-metadata"]
|
||||
windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
@@ -6921,6 +6919,7 @@ files = [
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
||||
@@ -6957,99 +6956,99 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pyzmq"
|
||||
version = "26.0.2"
|
||||
version = "26.0.3"
|
||||
description = "Python bindings for 0MQ"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pyzmq-26.0.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:1a60a03b01e8c9c58932ec0cca15b1712d911c2800eb82d4281bc1ae5b6dad50"},
|
||||
{file = "pyzmq-26.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:949067079e14ea1973bd740255e0840118c163d4bce8837f539d749f145cf5c3"},
|
||||
{file = "pyzmq-26.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37e7edfa6cf96d036a403775c96afa25058d1bb940a79786a9a2fc94a783abe3"},
|
||||
{file = "pyzmq-26.0.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:903cc7a84a7d4326b43755c368780800e035aa3d711deae84a533fdffa8755b0"},
|
||||
{file = "pyzmq-26.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cb2e41af165e5f327d06fbdd79a42a4e930267fade4e9f92d17f3ccce03f3a7"},
|
||||
{file = "pyzmq-26.0.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:55353b8189adcfc4c125fc4ce59d477744118e9c0ec379dd0999c5fa120ac4f5"},
|
||||
{file = "pyzmq-26.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f961423ff6236a752ced80057a20e623044df95924ed1009f844cde8b3a595f9"},
|
||||
{file = "pyzmq-26.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ba77fe84fe4f5f3dc0ef681a6d366685c8ffe1c8439c1d7530997b05ac06a04b"},
|
||||
{file = "pyzmq-26.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:52589f0a745ef61b9c75c872cf91f8c1f7c0668eb3dd99d7abd639d8c0fb9ca7"},
|
||||
{file = "pyzmq-26.0.2-cp310-cp310-win32.whl", hash = "sha256:b7b6d2a46c7afe2ad03ec8faf9967090c8ceae85c4d8934d17d7cae6f9062b64"},
|
||||
{file = "pyzmq-26.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:86531e20de249d9204cc6d8b13d5a30537748c78820215161d8a3b9ea58ca111"},
|
||||
{file = "pyzmq-26.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:f26a05029ecd2bd306b941ff8cb80f7620b7901421052bc429d238305b1cbf2f"},
|
||||
{file = "pyzmq-26.0.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:70770e296a9cb03d955540c99360aab861cbb3cba29516abbd106a15dbd91268"},
|
||||
{file = "pyzmq-26.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2740fd7161b39e178554ebf21aa5667a1c9ef0cd2cb74298fd4ef017dae7aec4"},
|
||||
{file = "pyzmq-26.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5e3706c32dea077faa42b1c92d825b7f86c866f72532d342e0be5e64d14d858"},
|
||||
{file = "pyzmq-26.0.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fa1416876194927f7723d6b7171b95e1115602967fc6bfccbc0d2d51d8ebae1"},
|
||||
{file = "pyzmq-26.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ef9a79a48794099c57dc2df00340b5d47c5caa1792f9ddb8c7a26b1280bd575"},
|
||||
{file = "pyzmq-26.0.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1c60fcdfa3229aeee4291c5d60faed3a813b18bdadb86299c4bf49e8e51e8605"},
|
||||
{file = "pyzmq-26.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e943c39c206b04df2eb5d71305761d7c3ca75fd49452115ea92db1b5b98dbdef"},
|
||||
{file = "pyzmq-26.0.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8da0ed8a598693731c76659880a668f4748b59158f26ed283a93f7f04d47447e"},
|
||||
{file = "pyzmq-26.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7bf51970b11d67096bede97cdbad0f4333f7664f4708b9b2acb352bf4faa3140"},
|
||||
{file = "pyzmq-26.0.2-cp311-cp311-win32.whl", hash = "sha256:6f8e6bd5d066be605faa9fe5ec10aa1a46ad9f18fc8646f2b9aaefc8fb575742"},
|
||||
{file = "pyzmq-26.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d03da3a0ae691b361edcb39530075461202f699ce05adbb15055a0e1c9bcaa4"},
|
||||
{file = "pyzmq-26.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:f84e33321b68ff00b60e9dbd1a483e31ab6022c577c8de525b8e771bd274ce68"},
|
||||
{file = "pyzmq-26.0.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:44c33ebd1c62a01db7fbc24e18bdda569d6639217d13d5929e986a2b0f69070d"},
|
||||
{file = "pyzmq-26.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ac04f904b4fce4afea9cdccbb78e24d468cb610a839d5a698853e14e2a3f9ecf"},
|
||||
{file = "pyzmq-26.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2133de5ba9adc5f481884ccb699eac9ce789708292945c05746880f95b241c0"},
|
||||
{file = "pyzmq-26.0.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7753c67c570d7fc80c2dc59b90ca1196f1224e0e2e29a548980c95fe0fe27fc1"},
|
||||
{file = "pyzmq-26.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d4e51632e6b12e65e8d9d7612446ecda2eda637a868afa7bce16270194650dd"},
|
||||
{file = "pyzmq-26.0.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d6c38806f6ecd0acf3104b8d7e76a206bcf56dadd6ce03720d2fa9d9157d5718"},
|
||||
{file = "pyzmq-26.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:48f496bbe14686b51cec15406323ae6942851e14022efd7fc0e2ecd092c5982c"},
|
||||
{file = "pyzmq-26.0.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e84a3161149c75bb7a7dc8646384186c34033e286a67fec1ad1bdedea165e7f4"},
|
||||
{file = "pyzmq-26.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:dabf796c67aa9f5a4fcc956d47f0d48b5c1ed288d628cf53aa1cf08e88654343"},
|
||||
{file = "pyzmq-26.0.2-cp312-cp312-win32.whl", hash = "sha256:3eee4c676af1b109f708d80ef0cf57ecb8aaa5900d1edaf90406aea7e0e20e37"},
|
||||
{file = "pyzmq-26.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:26721fec65846b3e4450dad050d67d31b017f97e67f7e0647b5f98aa47f828cf"},
|
||||
{file = "pyzmq-26.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:653955c6c233e90de128a1b8e882abc7216f41f44218056bd519969c8c413a15"},
|
||||
{file = "pyzmq-26.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:becd8d8fb068fbb5a52096efd83a2d8e54354383f691781f53a4c26aee944542"},
|
||||
{file = "pyzmq-26.0.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7a15e5465e7083c12517209c9dd24722b25e9b63c49a563922922fc03554eb35"},
|
||||
{file = "pyzmq-26.0.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e8158ac8616941f874841f9fa0f6d2f1466178c2ff91ea08353fdc19de0d40c2"},
|
||||
{file = "pyzmq-26.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea2c6a53e28c7066ea7db86fcc0b71d78d01b818bb11d4a4341ec35059885295"},
|
||||
{file = "pyzmq-26.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:bdbc7dab0b0e9c62c97b732899c4242e3282ba803bad668e03650b59b165466e"},
|
||||
{file = "pyzmq-26.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e74b6d5ef57bb65bf1b4a37453d8d86d88550dde3fb0f23b1f1a24e60c70af5b"},
|
||||
{file = "pyzmq-26.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ed4c6ee624ecbc77b18aeeb07bf0700d26571ab95b8f723f0d02e056b5bce438"},
|
||||
{file = "pyzmq-26.0.2-cp37-cp37m-win32.whl", hash = "sha256:8a98b3cb0484b83c19d8fb5524c8a469cd9f10e743f5904ac285d92678ee761f"},
|
||||
{file = "pyzmq-26.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:aa5f95d71b6eca9cec28aa0a2f8310ea53dea313b63db74932879ff860c1fb8d"},
|
||||
{file = "pyzmq-26.0.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:5ff56c76ce77b9805378a7a73032c17cbdb1a5b84faa1df03c5d3e306e5616df"},
|
||||
{file = "pyzmq-26.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bab697fc1574fee4b81da955678708567c43c813c84c91074e452bda5346c921"},
|
||||
{file = "pyzmq-26.0.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0c0fed8aa9ba0488ee1cbdaa304deea92d52fab43d373297002cfcc69c0a20c5"},
|
||||
{file = "pyzmq-26.0.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:606b922699fcec472ed814dda4dc3ff7c748254e0b26762a0ba21a726eb1c107"},
|
||||
{file = "pyzmq-26.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f0fd82bad4d199fa993fbf0ac586a7ac5879addbe436a35a389df7e0eb4c91"},
|
||||
{file = "pyzmq-26.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:166c5e41045939a52c01e6f374e493d9a6a45dfe677360d3e7026e38c42e8906"},
|
||||
{file = "pyzmq-26.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d566e859e8b8d5bca08467c093061774924b3d78a5ba290e82735b2569edc84b"},
|
||||
{file = "pyzmq-26.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:264ee0e72b72ca59279dc320deab5ae0fac0d97881aed1875ce4bde2e56ffde0"},
|
||||
{file = "pyzmq-26.0.2-cp38-cp38-win32.whl", hash = "sha256:3152bbd3a4744cbdd83dfb210ed701838b8b0c9065cef14671d6d91df12197d0"},
|
||||
{file = "pyzmq-26.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:bf77601d75ca692c179154b7e5943c286a4aaffec02c491afe05e60493ce95f2"},
|
||||
{file = "pyzmq-26.0.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:c770a7545b3deca2db185b59175e710a820dd4ed43619f4c02e90b0e227c6252"},
|
||||
{file = "pyzmq-26.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d47175f0a380bfd051726bc5c0054036ae4a5d8caf922c62c8a172ccd95c1a2a"},
|
||||
{file = "pyzmq-26.0.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9bce298c1ce077837e110367c321285dc4246b531cde1abfc27e4a5bbe2bed4d"},
|
||||
{file = "pyzmq-26.0.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c40b09b7e184d6e3e1be1c8af2cc320c0f9f610d8a5df3dd866e6e6e4e32b235"},
|
||||
{file = "pyzmq-26.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d420d856bf728713874cefb911398efe69e1577835851dd297a308a78c14c249"},
|
||||
{file = "pyzmq-26.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d792d3cab987058451e55c70c5926e93e2ceb68ca5a2334863bb903eb860c9cb"},
|
||||
{file = "pyzmq-26.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:83ec17729cf6d3464dab98a11e98294fcd50e6b17eaabd3d841515c23f6dbd3a"},
|
||||
{file = "pyzmq-26.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47c17d5ebfa88ae90f08960c97b49917098665b8cd8be31f2c24e177bcf37a0f"},
|
||||
{file = "pyzmq-26.0.2-cp39-cp39-win32.whl", hash = "sha256:d509685d1cd1d018705a811c5f9d5bc237790936ead6d06f6558b77e16cc7235"},
|
||||
{file = "pyzmq-26.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:c7cc8cc009e8f6989a6d86c96f87dae5f5fb07d6c96916cdc7719d546152c7db"},
|
||||
{file = "pyzmq-26.0.2-cp39-cp39-win_arm64.whl", hash = "sha256:3ada31cb879cd7532f4a85b501f4255c747d4813ab76b35c49ed510ce4865b45"},
|
||||
{file = "pyzmq-26.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0a6ceaddc830dd3ca86cb8451cf373d1f05215368e11834538c2902ed5205139"},
|
||||
{file = "pyzmq-26.0.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a967681463aa7a99eb9a62bb18229b653b45c10ff0947b31cc0837a83dfb86f"},
|
||||
{file = "pyzmq-26.0.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6472a73bc115bc40a2076609a90894775abe6faf19a78375675a2f889a613071"},
|
||||
{file = "pyzmq-26.0.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d6aea92bcccfe5e5524d3c70a6f16ffdae548390ddad26f4207d55c55a40593"},
|
||||
{file = "pyzmq-26.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e025f6351e49d48a5aa2f5a09293aa769b0ee7369c25bed551647234b7fa0c75"},
|
||||
{file = "pyzmq-26.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:40bd7ebe4dbb37d27f0c56e2a844f360239343a99be422085e13e97da13f73f9"},
|
||||
{file = "pyzmq-26.0.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1dd40d586ad6f53764104df6e01810fe1b4e88fd353774629a5e6fe253813f79"},
|
||||
{file = "pyzmq-26.0.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f2aca15e9ad8c8657b5b3d7ae3d1724dc8c1c1059c06b4b674c3aa36305f4930"},
|
||||
{file = "pyzmq-26.0.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:450ec234736732eb0ebeffdb95a352450d4592f12c3e087e2a9183386d22c8bf"},
|
||||
{file = "pyzmq-26.0.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f43be2bebbd09360a2f23af83b243dc25ffe7b583ea8c722e6df03e03a55f02f"},
|
||||
{file = "pyzmq-26.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:867f55e54aff254940bcec5eec068e7c0ac1e6bf360ab91479394a8bf356b0e6"},
|
||||
{file = "pyzmq-26.0.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b4dbc033c5ad46f8c429bf238c25a889b8c1d86bfe23a74e1031a991cb3f0000"},
|
||||
{file = "pyzmq-26.0.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6e8dd2961462e337e21092ec2da0c69d814dcb1b6e892955a37444a425e9cfb8"},
|
||||
{file = "pyzmq-26.0.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35391e72df6c14a09b697c7b94384947c1dd326aca883ff98ff137acdf586c33"},
|
||||
{file = "pyzmq-26.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:1c3d3c92fa54eda94ab369ca5b8d35059987c326ba5e55326eb068862f64b1fc"},
|
||||
{file = "pyzmq-26.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7aa61a9cc4f0523373e31fc9255bf4567185a099f85ca3598e64de484da3ab2"},
|
||||
{file = "pyzmq-26.0.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee53a8191271f144cc20b12c19daa9f1546adc84a2f33839e3338039b55c373c"},
|
||||
{file = "pyzmq-26.0.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac60a980f07fa988983f7bfe6404ef3f1e4303f5288a01713bc1266df6d18783"},
|
||||
{file = "pyzmq-26.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88896b1b4817d7b2fe1ec7205c4bbe07bf5d92fb249bf2d226ddea8761996068"},
|
||||
{file = "pyzmq-26.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:18dfffe23751edee917764ffa133d5d3fef28dfd1cf3adebef8c90bc854c74c4"},
|
||||
{file = "pyzmq-26.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6926dd14cfe6967d3322640b6d5c3c3039db71716a5e43cca6e3b474e73e0b36"},
|
||||
{file = "pyzmq-26.0.2.tar.gz", hash = "sha256:f0f9bb370449158359bb72a3e12c658327670c0ffe6fbcd1af083152b64f9df0"},
|
||||
{file = "pyzmq-26.0.3-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625"},
|
||||
{file = "pyzmq-26.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90"},
|
||||
{file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de"},
|
||||
{file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be"},
|
||||
{file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee"},
|
||||
{file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf"},
|
||||
{file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59"},
|
||||
{file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc"},
|
||||
{file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8"},
|
||||
{file = "pyzmq-26.0.3-cp310-cp310-win32.whl", hash = "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537"},
|
||||
{file = "pyzmq-26.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47"},
|
||||
{file = "pyzmq-26.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7"},
|
||||
{file = "pyzmq-26.0.3-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32"},
|
||||
{file = "pyzmq-26.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd"},
|
||||
{file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7"},
|
||||
{file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9"},
|
||||
{file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527"},
|
||||
{file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a"},
|
||||
{file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5"},
|
||||
{file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd"},
|
||||
{file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83"},
|
||||
{file = "pyzmq-26.0.3-cp311-cp311-win32.whl", hash = "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3"},
|
||||
{file = "pyzmq-26.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500"},
|
||||
{file = "pyzmq-26.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94"},
|
||||
{file = "pyzmq-26.0.3-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753"},
|
||||
{file = "pyzmq-26.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4"},
|
||||
{file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b"},
|
||||
{file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12"},
|
||||
{file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02"},
|
||||
{file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20"},
|
||||
{file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77"},
|
||||
{file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2"},
|
||||
{file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798"},
|
||||
{file = "pyzmq-26.0.3-cp312-cp312-win32.whl", hash = "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0"},
|
||||
{file = "pyzmq-26.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf"},
|
||||
{file = "pyzmq-26.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b"},
|
||||
{file = "pyzmq-26.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5"},
|
||||
{file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b"},
|
||||
{file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa"},
|
||||
{file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450"},
|
||||
{file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987"},
|
||||
{file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a"},
|
||||
{file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5"},
|
||||
{file = "pyzmq-26.0.3-cp37-cp37m-win32.whl", hash = "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf"},
|
||||
{file = "pyzmq-26.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a"},
|
||||
{file = "pyzmq-26.0.3-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18"},
|
||||
{file = "pyzmq-26.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d"},
|
||||
{file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6"},
|
||||
{file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad"},
|
||||
{file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad"},
|
||||
{file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67"},
|
||||
{file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c"},
|
||||
{file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97"},
|
||||
{file = "pyzmq-26.0.3-cp38-cp38-win32.whl", hash = "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc"},
|
||||
{file = "pyzmq-26.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972"},
|
||||
{file = "pyzmq-26.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606"},
|
||||
{file = "pyzmq-26.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f"},
|
||||
{file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5"},
|
||||
{file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8"},
|
||||
{file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620"},
|
||||
{file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4"},
|
||||
{file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab"},
|
||||
{file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920"},
|
||||
{file = "pyzmq-26.0.3-cp39-cp39-win32.whl", hash = "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879"},
|
||||
{file = "pyzmq-26.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2"},
|
||||
{file = "pyzmq-26.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381"},
|
||||
{file = "pyzmq-26.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de"},
|
||||
{file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35"},
|
||||
{file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84"},
|
||||
{file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223"},
|
||||
{file = "pyzmq-26.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c"},
|
||||
{file = "pyzmq-26.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81"},
|
||||
{file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1"},
|
||||
{file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5"},
|
||||
{file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709"},
|
||||
{file = "pyzmq-26.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6"},
|
||||
{file = "pyzmq-26.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09"},
|
||||
{file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7"},
|
||||
{file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2"},
|
||||
{file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480"},
|
||||
{file = "pyzmq-26.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce"},
|
||||
{file = "pyzmq-26.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17"},
|
||||
{file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4"},
|
||||
{file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67"},
|
||||
{file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a"},
|
||||
{file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d"},
|
||||
{file = "pyzmq-26.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad"},
|
||||
{file = "pyzmq-26.0.3.tar.gz", hash = "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -7078,43 +7077,43 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "rubicon-objc"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
description = "A bridge between an Objective C runtime environment and Python."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "rubicon-objc-0.4.8.tar.gz", hash = "sha256:f7bede0f0e73dde07083785984c75b3acf76116298743758ea5c51b3ebb81978"},
|
||||
{file = "rubicon_objc-0.4.8-py3-none-any.whl", hash = "sha256:59633f622e3c6e740bd9b3c4b60d0258fc2d30632b05d46cba12fae69539cfd3"},
|
||||
{file = "rubicon_objc-0.4.9-py3-none-any.whl", hash = "sha256:c351b3800cf74c8c23f7d534f008fd5de46c63818de7a44de96daffdb3ed8b8c"},
|
||||
{file = "rubicon_objc-0.4.9.tar.gz", hash = "sha256:3d77a5b2d10cb1e49679aa90b7824b46f67b3fd636229aa4a1b902d24aec6a58"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit (==3.5.0)", "pre-commit (==3.7.0)", "pytest (==8.1.1)", "pytest-tldr (==0.2.5)", "setuptools-scm (==8.0.4)", "tox (==4.14.2)"]
|
||||
docs = ["furo (==2024.1.29)", "pyenchant (==3.2.2)", "sphinx (==7.1.2)", "sphinx (==7.2.6)", "sphinx-autobuild (==2021.3.14)", "sphinx-autobuild (==2024.2.4)", "sphinx-copybutton (==0.5.2)", "sphinx-tabs (==3.4.5)", "sphinxcontrib-spelling (==8.0.0)"]
|
||||
dev = ["pre-commit (==3.5.0)", "pre-commit (==3.7.0)", "pytest (==8.2.0)", "pytest-tldr (==0.2.5)", "setuptools-scm (==8.0.4)", "tox (==4.15.0)"]
|
||||
docs = ["furo (==2024.4.27)", "pyenchant (==3.2.2)", "sphinx (==7.1.2)", "sphinx (==7.3.7)", "sphinx-autobuild (==2021.3.14)", "sphinx-autobuild (==2024.4.16)", "sphinx-copybutton (==0.5.2)", "sphinx-tabs (==3.4.5)", "sphinxcontrib-spelling (==8.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d14dc8953f8af7e003a485ef560bbefa5f8cc1ad994eebb5b12136049bbccc5"},
|
||||
{file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:24016ed18db3dc9786af103ff49c03bdf408ea253f3cb9e3638f39ac9cf2d483"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2e06459042ac841ed510196c350ba35a9b24a643e23db60d79b2db92af0c2b"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3afabaf7ba8e9c485a14ad8f4122feff6b2b93cc53cd4dad2fd24ae35112d5c5"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:799eb468ea6bc54b95527143a4ceaf970d5aa3613050c6cff54c85fda3fde480"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ec4ba9436a51527fb6931a8839af4c36a5481f8c19e8f5e42c2f7ad3a49f5069"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a2243f8f434e487c2a010c7252150b1fdf019035130f41b77626f5655c9ca22"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8772130a063f3eebdf7095da00c0b9898bd1774c43b336272c3e98667d4fb8fa"},
|
||||
{file = "ruff-0.4.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ab165ef5d72392b4ebb85a8b0fbd321f69832a632e07a74794c0e598e7a8376"},
|
||||
{file = "ruff-0.4.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f32cadf44c2020e75e0c56c3408ed1d32c024766bd41aedef92aa3ca28eef68"},
|
||||
{file = "ruff-0.4.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:22e306bf15e09af45ca812bc42fa59b628646fa7c26072555f278994890bc7ac"},
|
||||
{file = "ruff-0.4.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:82986bb77ad83a1719c90b9528a9dd663c9206f7c0ab69282af8223566a0c34e"},
|
||||
{file = "ruff-0.4.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:652e4ba553e421a6dc2a6d4868bc3b3881311702633eb3672f9f244ded8908cd"},
|
||||
{file = "ruff-0.4.2-py3-none-win32.whl", hash = "sha256:7891ee376770ac094da3ad40c116258a381b86c7352552788377c6eb16d784fe"},
|
||||
{file = "ruff-0.4.2-py3-none-win_amd64.whl", hash = "sha256:5ec481661fb2fd88a5d6cf1f83403d388ec90f9daaa36e40e2c003de66751798"},
|
||||
{file = "ruff-0.4.2-py3-none-win_arm64.whl", hash = "sha256:cbd1e87c71bca14792948c4ccb51ee61c3296e164019d2d484f3eaa2d360dfaf"},
|
||||
{file = "ruff-0.4.2.tar.gz", hash = "sha256:33bcc160aee2520664bc0859cfeaebc84bb7323becff3f303b8f1f2d81cb4edc"},
|
||||
{file = "ruff-0.4.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b70800c290f14ae6fcbb41bbe201cf62dfca024d124a1f373e76371a007454ce"},
|
||||
{file = "ruff-0.4.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:08a0d6a22918ab2552ace96adeaca308833873a4d7d1d587bb1d37bae8728eb3"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba1f14df3c758dd7de5b55fbae7e1c8af238597961e5fb628f3de446c3c40c5"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:819fb06d535cc76dfddbfe8d3068ff602ddeb40e3eacbc90e0d1272bb8d97113"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bfc9e955e6dc6359eb6f82ea150c4f4e82b660e5b58d9a20a0e42ec3bb6342b"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:510a67d232d2ebe983fddea324dbf9d69b71c4d2dfeb8a862f4a127536dd4cfb"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9ff11cd9a092ee7680a56d21f302bdda14327772cd870d806610a3503d001f"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29efff25bf9ee685c2c8390563a5b5c006a3fee5230d28ea39f4f75f9d0b6f2f"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b00e0bcccf0fc8d7186ed21e311dffd19761cb632241a6e4fe4477cc80ef6e"},
|
||||
{file = "ruff-0.4.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:262f5635e2c74d80b7507fbc2fac28fe0d4fef26373bbc62039526f7722bca1b"},
|
||||
{file = "ruff-0.4.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7363691198719c26459e08cc17c6a3dac6f592e9ea3d2fa772f4e561b5fe82a3"},
|
||||
{file = "ruff-0.4.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eeb039f8428fcb6725bb63cbae92ad67b0559e68b5d80f840f11914afd8ddf7f"},
|
||||
{file = "ruff-0.4.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:927b11c1e4d0727ce1a729eace61cee88a334623ec424c0b1c8fe3e5f9d3c865"},
|
||||
{file = "ruff-0.4.3-py3-none-win32.whl", hash = "sha256:25cacda2155778beb0d064e0ec5a3944dcca9c12715f7c4634fd9d93ac33fd30"},
|
||||
{file = "ruff-0.4.3-py3-none-win_amd64.whl", hash = "sha256:7a1c3a450bc6539ef00da6c819fb1b76b6b065dec585f91456e7c0d6a0bbc725"},
|
||||
{file = "ruff-0.4.3-py3-none-win_arm64.whl", hash = "sha256:71ca5f8ccf1121b95a59649482470c5601c60a416bf189d553955b0338e34614"},
|
||||
{file = "ruff-0.4.3.tar.gz", hash = "sha256:ff0a3ef2e3c4b6d133fbedcf9586abfbe38d076041f2dc18ffb2c7e0485d5a07"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7193,13 +7192,13 @@ stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.0.1"
|
||||
version = "2.1.1"
|
||||
description = "Python client for Sentry (https://sentry.io)"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "sentry_sdk-2.0.1-py2.py3-none-any.whl", hash = "sha256:b54c54a2160f509cf2757260d0cf3885b608c6192c2555a3857e3a4d0f84bdb3"},
|
||||
{file = "sentry_sdk-2.0.1.tar.gz", hash = "sha256:c278e0f523f6f0ee69dc43ad26dcdb1202dffe5ac326ae31472e012d941bee21"},
|
||||
{file = "sentry_sdk-2.1.1-py2.py3-none-any.whl", hash = "sha256:99aeb78fb76771513bd3b2829d12613130152620768d00cd3e45ac00cb17950f"},
|
||||
{file = "sentry_sdk-2.1.1.tar.gz", hash = "sha256:95d8c0bb41c8b0bc37ab202c2c4a295bb84398ee05f4cdce55051cd75b926ec1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -7208,6 +7207,7 @@ urllib3 = ">=1.26.11"
|
||||
|
||||
[package.extras]
|
||||
aiohttp = ["aiohttp (>=3.5)"]
|
||||
anthropic = ["anthropic (>=0.16)"]
|
||||
arq = ["arq (>=0.23)"]
|
||||
asyncpg = ["asyncpg (>=0.23)"]
|
||||
beam = ["apache-beam (>=2.12)"]
|
||||
@@ -7223,6 +7223,8 @@ flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"]
|
||||
grpcio = ["grpcio (>=1.21.1)"]
|
||||
httpx = ["httpx (>=0.16.0)"]
|
||||
huey = ["huey (>=2)"]
|
||||
huggingface-hub = ["huggingface-hub (>=0.22)"]
|
||||
langchain = ["langchain (>=0.0.210)"]
|
||||
loguru = ["loguru (>=0.5)"]
|
||||
openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"]
|
||||
opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
|
||||
@@ -7745,13 +7747,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "4.66.2"
|
||||
version = "4.66.4"
|
||||
description = "Fast, Extensible Progress Meter"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"},
|
||||
{file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"},
|
||||
{file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"},
|
||||
{file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
||||
+1
-1
Submodule rednose_repo updated: 24c50f57bb...72b3479bab
@@ -1,34 +0,0 @@
|
||||
# openpilot releases
|
||||
|
||||
|
||||
## terms
|
||||
|
||||
- `channel` - a named version of openpilot (git branch, casync caibx) which receives updates
|
||||
- `build` - a copy of openpilot ready for distribution, already built for a specific device
|
||||
- `build_style` - type of build, either `debug` or `release`
|
||||
- `debug` - build with `ALLOW_DEBUG=true`, can test experimental features like longitudinal on alpha cars
|
||||
- `release` - build with `ALLOW_DEBUG=false`, experimental features disabled
|
||||
|
||||
|
||||
## openpilot channels
|
||||
|
||||
| channel | build_style | description |
|
||||
| ----------- | ----------- | ---------- |
|
||||
| release | `release` | stable release of openpilot |
|
||||
| staging | `release` | release candidate of openpilot for final verification |
|
||||
| nightly | `release` | generated nightly from last commit passing CI tests |
|
||||
| master | `debug` | current master commit with experimental features enabled |
|
||||
| git branches | `debug` | installed manually, experimental features enabled, build required |
|
||||
|
||||
|
||||
## build
|
||||
|
||||
`release/build_release.sh <build_dir>` - creates an openpilot build into `build_dir`, ready for distribution
|
||||
|
||||
## packaging a casync release
|
||||
|
||||
`release/package_casync_build.py <build_dir>` - packages an openpilot build into a casync tar and uploads to `openpilot-releases`
|
||||
|
||||
## release builds
|
||||
|
||||
to create a release build, set `RELEASE=1` environment variable when running the build script
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/usr/bin/bash -e
|
||||
|
||||
# git diff --name-status origin/release3-staging | grep "^A" | less
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
|
||||
cd $DIR
|
||||
|
||||
BUILD_DIR=/data/openpilot
|
||||
SOURCE_DIR="$(git rev-parse --show-toplevel)"
|
||||
|
||||
if [ -f /TICI ]; then
|
||||
FILES_SRC="release/files_tici"
|
||||
else
|
||||
echo "no release files set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$RELEASE_BRANCH" ]; then
|
||||
echo "RELEASE_BRANCH is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# set git identity
|
||||
source $DIR/identity.sh
|
||||
|
||||
echo "[-] Setting up repo T=$SECONDS"
|
||||
rm -rf $BUILD_DIR
|
||||
mkdir -p $BUILD_DIR
|
||||
cd $BUILD_DIR
|
||||
git init
|
||||
git remote add origin git@github.com:commaai/openpilot.git
|
||||
git checkout --orphan $RELEASE_BRANCH
|
||||
|
||||
# do the files copy
|
||||
echo "[-] copying files T=$SECONDS"
|
||||
cd $SOURCE_DIR
|
||||
cp -pR --parents $(cat release/files_common) $BUILD_DIR/
|
||||
cp -pR --parents $(cat $FILES_SRC) $BUILD_DIR/
|
||||
|
||||
# in the directory
|
||||
cd $BUILD_DIR
|
||||
|
||||
rm -f panda/board/obj/panda.bin.signed
|
||||
rm -f panda/board/obj/panda_h7.bin.signed
|
||||
|
||||
VERSION=$(cat common/version.h | awk -F[\"-] '{print $2}')
|
||||
echo "#define COMMA_VERSION \"$VERSION-release\"" > common/version.h
|
||||
|
||||
echo "[-] committing version $VERSION T=$SECONDS"
|
||||
git add -f .
|
||||
git commit -a -m "openpilot v$VERSION release"
|
||||
|
||||
# Build
|
||||
export PYTHONPATH="$BUILD_DIR"
|
||||
scons -j$(nproc)
|
||||
|
||||
# release panda fw
|
||||
CERT=/data/pandaextra/certs/release RELEASE=1 scons -j$(nproc) panda/
|
||||
|
||||
# Ensure no submodules in release
|
||||
if test "$(git submodule--helper list | wc -l)" -gt "0"; then
|
||||
echo "submodules found:"
|
||||
git submodule--helper list
|
||||
exit 1
|
||||
fi
|
||||
git submodule status
|
||||
|
||||
# Cleanup
|
||||
find . -name '*.a' -delete
|
||||
find . -name '*.o' -delete
|
||||
find . -name '*.os' -delete
|
||||
find . -name '*.pyc' -delete
|
||||
find . -name 'moc_*' -delete
|
||||
find . -name '__pycache__' -delete
|
||||
rm -rf .sconsign.dblite Jenkinsfile release/
|
||||
rm selfdrive/modeld/models/supercombo.onnx
|
||||
|
||||
# Restore third_party
|
||||
git checkout third_party/
|
||||
|
||||
# Mark as prebuilt release
|
||||
touch prebuilt
|
||||
|
||||
# Add built files to git
|
||||
git add -f .
|
||||
git commit --amend -m "openpilot v$VERSION"
|
||||
|
||||
# Run tests
|
||||
TEST_FILES="tools/"
|
||||
cd $SOURCE_DIR
|
||||
cp -pR -n --parents $TEST_FILES $BUILD_DIR/
|
||||
cd $BUILD_DIR
|
||||
RELEASE=1 selfdrive/test/test_onroad.py
|
||||
#selfdrive/manager/test/test_manager.py
|
||||
#selfdrive/car/tests/test_car_interfaces.py
|
||||
rm -rf $TEST_FILES
|
||||
|
||||
if [ ! -z "$RELEASE_BRANCH" ]; then
|
||||
echo "[-] pushing release T=$SECONDS"
|
||||
git push -f origin $RELEASE_BRANCH:$RELEASE_BRANCH
|
||||
fi
|
||||
|
||||
echo "[-] done T=$SECONDS"
|
||||
+67
-13
@@ -1,40 +1,72 @@
|
||||
#!/usr/bin/bash
|
||||
#!/usr/bin/bash -e
|
||||
|
||||
set -e
|
||||
# git diff --name-status origin/release3-staging | grep "^A" | less
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
SOURCE_DIR="$(git -C $DIR rev-parse --show-toplevel)"
|
||||
BUILD_DIR=${1:-$(mktemp -d)}
|
||||
|
||||
cd $DIR
|
||||
|
||||
BUILD_DIR=/data/openpilot
|
||||
SOURCE_DIR="$(git rev-parse --show-toplevel)"
|
||||
|
||||
if [ -f /TICI ]; then
|
||||
FILES_SRC="release/files_tici"
|
||||
else
|
||||
FILES_SRC="release/files_pc"
|
||||
echo "no release files set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Building openpilot into $BUILD_DIR"
|
||||
if [ -z "$RELEASE_BRANCH" ]; then
|
||||
echo "RELEASE_BRANCH is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# set git identity
|
||||
source $DIR/identity.sh
|
||||
|
||||
echo "[-] Setting up repo T=$SECONDS"
|
||||
rm -rf $BUILD_DIR
|
||||
mkdir -p $BUILD_DIR
|
||||
cd $BUILD_DIR
|
||||
git init
|
||||
git remote add origin git@github.com:commaai/openpilot.git
|
||||
git checkout --orphan $RELEASE_BRANCH
|
||||
|
||||
# Copy required files to BUILD_DIR
|
||||
# do the files copy
|
||||
echo "[-] copying files T=$SECONDS"
|
||||
cd $SOURCE_DIR
|
||||
cp -pR --parents $(cat release/files_common) $BUILD_DIR/
|
||||
cp -pR --parents $(cat $FILES_SRC) $BUILD_DIR/
|
||||
|
||||
# Build + cleanup
|
||||
# in the directory
|
||||
cd $BUILD_DIR
|
||||
export PYTHONPATH="$BUILD_DIR"
|
||||
|
||||
rm -f panda/board/obj/panda.bin.signed
|
||||
rm -f panda/board/obj/panda_h7.bin.signed
|
||||
|
||||
if [ -n "$RELEASE" ]; then
|
||||
export CERT=/data/pandaextra/certs/release
|
||||
fi
|
||||
VERSION=$(cat common/version.h | awk -F[\"-] '{print $2}')
|
||||
echo "#define COMMA_VERSION \"$VERSION-release\"" > common/version.h
|
||||
|
||||
echo "[-] committing version $VERSION T=$SECONDS"
|
||||
git add -f .
|
||||
git commit -a -m "openpilot v$VERSION release"
|
||||
|
||||
# Build
|
||||
export PYTHONPATH="$BUILD_DIR"
|
||||
scons -j$(nproc)
|
||||
|
||||
# release panda fw
|
||||
CERT=/data/pandaextra/certs/release RELEASE=1 scons -j$(nproc) panda/
|
||||
|
||||
# Ensure no submodules in release
|
||||
if test "$(git submodule--helper list | wc -l)" -gt "0"; then
|
||||
echo "submodules found:"
|
||||
git submodule--helper list
|
||||
exit 1
|
||||
fi
|
||||
git submodule status
|
||||
|
||||
# Cleanup
|
||||
find . -name '*.a' -delete
|
||||
find . -name '*.o' -delete
|
||||
@@ -45,7 +77,29 @@ find . -name '__pycache__' -delete
|
||||
rm -rf .sconsign.dblite Jenkinsfile release/
|
||||
rm selfdrive/modeld/models/supercombo.onnx
|
||||
|
||||
# Restore third_party
|
||||
git checkout third_party/
|
||||
|
||||
# Mark as prebuilt release
|
||||
touch prebuilt
|
||||
|
||||
echo "----- openpilot has been built to $BUILD_DIR -----"
|
||||
# Add built files to git
|
||||
git add -f .
|
||||
git commit --amend -m "openpilot v$VERSION"
|
||||
|
||||
# Run tests
|
||||
TEST_FILES="tools/"
|
||||
cd $SOURCE_DIR
|
||||
cp -pR -n --parents $TEST_FILES $BUILD_DIR/
|
||||
cd $BUILD_DIR
|
||||
RELEASE=1 selfdrive/test/test_onroad.py
|
||||
#selfdrive/manager/test/test_manager.py
|
||||
selfdrive/car/tests/test_car_interfaces.py
|
||||
rm -rf $TEST_FILES
|
||||
|
||||
if [ ! -z "$RELEASE_BRANCH" ]; then
|
||||
echo "[-] pushing release T=$SECONDS"
|
||||
git push -f origin $RELEASE_BRANCH:$RELEASE_BRANCH
|
||||
fi
|
||||
|
||||
echo "[-] done T=$SECONDS"
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import tempfile
|
||||
import time
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.system.hardware.tici.agnos import StreamingDecompressor, unsparsify, noop, AGNOS_MANIFEST_FILE
|
||||
from openpilot.system.updated.casync.common import create_casync_from_file
|
||||
from release.package_casync_build import upload_casync_release
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="creates a casync release")
|
||||
parser.add_argument("--manifest", type=str, help="json manifest to create agnos release from", \
|
||||
default=str(pathlib.Path(BASEDIR) / AGNOS_MANIFEST_FILE))
|
||||
args = parser.parse_args()
|
||||
|
||||
manifest_file = pathlib.Path(args.manifest)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
working_dir = pathlib.Path(temp_dir)
|
||||
casync_dir = working_dir / "casync"
|
||||
casync_dir.mkdir()
|
||||
|
||||
agnos_casync_dir = casync_dir / "agnos"
|
||||
agnos_casync_dir.mkdir()
|
||||
|
||||
entry_path = working_dir / "entry"
|
||||
|
||||
with open(manifest_file) as f:
|
||||
manifest = json.load(f)
|
||||
|
||||
for entry in manifest:
|
||||
print(f"creating casync agnos build from {entry}")
|
||||
start = time.monotonic()
|
||||
downloader = StreamingDecompressor(entry['url'])
|
||||
|
||||
parse_func = unsparsify if entry['sparse'] else noop
|
||||
|
||||
parsed_chunks = parse_func(downloader)
|
||||
|
||||
size = entry["size"]
|
||||
|
||||
cur = 0
|
||||
with open(entry_path, "wb") as f:
|
||||
for chunk in parsed_chunks:
|
||||
f.write(chunk)
|
||||
|
||||
print(f"downloaded in {time.monotonic() - start}")
|
||||
|
||||
start = time.monotonic()
|
||||
agnos_filename = os.path.basename(entry["url"]).split(".")[0]
|
||||
create_casync_from_file(entry_path, agnos_casync_dir, agnos_filename)
|
||||
print(f"created casnc in {time.monotonic() - start}")
|
||||
|
||||
upload_casync_release(casync_dir)
|
||||
@@ -1,108 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# packages a casync release, uploads to azure, and creates a manifest
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import tempfile
|
||||
|
||||
from openpilot.system.hardware.tici.agnos import AGNOS_MANIFEST_FILE, get_partition_path
|
||||
from openpilot.system.updated.casync.common import create_build_metadata_file, create_casync_release
|
||||
from openpilot.system.version import get_build_metadata
|
||||
from openpilot.tools.lib.azure_container import AzureContainer
|
||||
|
||||
|
||||
BASE_URL = "https://commadist.blob.core.windows.net"
|
||||
|
||||
OPENPILOT_RELEASES = f"{BASE_URL}/openpilot-releases/openpilot"
|
||||
AGNOS_RELEASES = f"{BASE_URL}/openpilot-releases/agnos"
|
||||
|
||||
|
||||
def create_casync_caibx(target_dir: pathlib.Path, output_dir: pathlib.Path):
|
||||
output_dir.mkdir()
|
||||
build_metadata = get_build_metadata()
|
||||
build_metadata.openpilot.build_style = "release" if os.environ.get("RELEASE", None) is not None else "debug"
|
||||
|
||||
create_build_metadata_file(target_dir, build_metadata)
|
||||
|
||||
digest, caibx = create_casync_release(target_dir, output_dir, build_metadata.canonical)
|
||||
|
||||
print(f"Created casync release from {target_dir} to {caibx} with digest {digest}")
|
||||
|
||||
|
||||
def upload_casync_release(casync_dir: pathlib.Path):
|
||||
if "AZURE_TOKEN_OPENPILOT_RELEASES" in os.environ:
|
||||
os.environ["AZURE_TOKEN"] = os.environ["AZURE_TOKEN_OPENPILOT_RELEASES"]
|
||||
|
||||
OPENPILOT_RELEASES_CONTAINER = AzureContainer("commadist", "openpilot-releases")
|
||||
|
||||
for f in casync_dir.rglob("*"):
|
||||
if f.is_file():
|
||||
blob_name = f.relative_to(casync_dir)
|
||||
print(f"uploading {f} to {blob_name}")
|
||||
OPENPILOT_RELEASES_CONTAINER.upload_file(str(f), str(blob_name), overwrite=True)
|
||||
|
||||
|
||||
def create_partition_manifest(partition):
|
||||
agnos_filename = os.path.basename(partition["url"]).split(".")[0]
|
||||
|
||||
return {
|
||||
"type": "partition",
|
||||
"casync": {
|
||||
"caibx": f"{AGNOS_RELEASES}/{agnos_filename}.caibx"
|
||||
},
|
||||
"path": get_partition_path(0, partition),
|
||||
"ab": True,
|
||||
"size": partition["size"],
|
||||
"full_check": partition["full_check"],
|
||||
"hash_raw": partition["hash_raw"],
|
||||
}
|
||||
|
||||
|
||||
def create_openpilot_manifest(build_metadata):
|
||||
return {
|
||||
"type": "path_tarred",
|
||||
"path": "/data/openpilot",
|
||||
"casync": {
|
||||
"caibx": f"{OPENPILOT_RELEASES}/{build_metadata.canonical}.caibx"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def create_manifest(target_dir):
|
||||
with open(pathlib.Path(target_dir) / AGNOS_MANIFEST_FILE) as f:
|
||||
agnos_manifest = json.load(f)
|
||||
|
||||
build_metadata = get_build_metadata(args.target_dir)
|
||||
|
||||
return {
|
||||
"build_metadata": dataclasses.asdict(build_metadata),
|
||||
"manifest": [
|
||||
*[create_partition_manifest(entry) for entry in agnos_manifest],
|
||||
create_openpilot_manifest(build_metadata)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="creates a casync release")
|
||||
parser.add_argument("target_dir", type=str, help="path to a release build of openpilot to create release from")
|
||||
args = parser.parse_args()
|
||||
|
||||
target_dir = pathlib.Path(args.target_dir)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
casync_dir = pathlib.Path(temp_dir) / "casync"
|
||||
casync_dir.mkdir(parents=True)
|
||||
|
||||
manifest_file = pathlib.Path(temp_dir) / "manifest.json"
|
||||
|
||||
create_casync_caibx(target_dir, casync_dir / "openpilot")
|
||||
upload_casync_release(casync_dir)
|
||||
manifest = create_manifest(target_dir)
|
||||
|
||||
print(json.dumps(manifest, indent=2))
|
||||
@@ -467,6 +467,10 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local
|
||||
cookie="jwt=" + identity_token,
|
||||
enable_multithread=True)
|
||||
|
||||
# Set TOS to keep connection responsive while under load.
|
||||
# DSCP of 36/HDD_LINUX_AC_VI with the minimum delay flag
|
||||
ws.sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, 0x90)
|
||||
|
||||
ssock, csock = socket.socketpair()
|
||||
local_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
local_sock.connect(('127.0.0.1', local_port))
|
||||
@@ -657,10 +661,12 @@ def stat_handler(end_event: threading.Event) -> None:
|
||||
def ws_proxy_recv(ws: WebSocket, local_sock: socket.socket, ssock: socket.socket, end_event: threading.Event, global_end_event: threading.Event) -> None:
|
||||
while not (end_event.is_set() or global_end_event.is_set()):
|
||||
try:
|
||||
data = ws.recv()
|
||||
if isinstance(data, str):
|
||||
data = data.encode("utf-8")
|
||||
local_sock.sendall(data)
|
||||
r = select.select((ws.sock,), (), (), 30)
|
||||
if r[0]:
|
||||
data = ws.recv()
|
||||
if isinstance(data, str):
|
||||
data = data.encode("utf-8")
|
||||
local_sock.sendall(data)
|
||||
except WebSocketTimeoutException:
|
||||
pass
|
||||
except Exception:
|
||||
@@ -670,6 +676,7 @@ def ws_proxy_recv(ws: WebSocket, local_sock: socket.socket, ssock: socket.socket
|
||||
cloudlog.debug("athena.ws_proxy_recv closing sockets")
|
||||
ssock.close()
|
||||
local_sock.close()
|
||||
ws.close()
|
||||
cloudlog.debug("athena.ws_proxy_recv done closing sockets")
|
||||
|
||||
end_event.set()
|
||||
|
||||
@@ -43,6 +43,8 @@ class MockApi():
|
||||
|
||||
|
||||
class MockWebsocket():
|
||||
sock = socket.socket()
|
||||
|
||||
def __init__(self, recv_queue, send_queue):
|
||||
self.recv_queue = recv_queue
|
||||
self.send_queue = send_queue
|
||||
@@ -56,6 +58,9 @@ class MockWebsocket():
|
||||
def send(self, data, opcode):
|
||||
self.send_queue.put_nowait((data, opcode))
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def do_PUT(self):
|
||||
|
||||
@@ -78,5 +78,6 @@ private:
|
||||
int bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len, unsigned int timeout);
|
||||
int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout);
|
||||
int spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout);
|
||||
int lltransfer(spi_ioc_transfer &t);
|
||||
};
|
||||
#endif
|
||||
|
||||
+30
-4
@@ -268,7 +268,7 @@ int PandaSpiHandle::wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout,
|
||||
tx_buf[0] = tx;
|
||||
|
||||
while (true) {
|
||||
int ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
|
||||
int ret = lltransfer(transfer);
|
||||
if (ret < 0) {
|
||||
LOGE("SPI: failed to send ACK request");
|
||||
return ret;
|
||||
@@ -291,6 +291,32 @@ int PandaSpiHandle::wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PandaSpiHandle::lltransfer(spi_ioc_transfer &t) {
|
||||
static const double err_prob = std::stod(util::getenv("SPI_ERR_PROB", "-1"));
|
||||
|
||||
if (err_prob > 0) {
|
||||
if ((static_cast<double>(rand()) / RAND_MAX) < err_prob) {
|
||||
printf("transfer len error\n");
|
||||
t.len = rand() % SPI_BUF_SIZE;
|
||||
}
|
||||
if ((static_cast<double>(rand()) / RAND_MAX) < err_prob && t.tx_buf != (uint64_t)NULL) {
|
||||
printf("corrupting TX\n");
|
||||
memset((uint8_t*)t.tx_buf, (uint8_t)(rand() % 256), rand() % (t.len+1));
|
||||
}
|
||||
}
|
||||
|
||||
int ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &t);
|
||||
|
||||
if (err_prob > 0) {
|
||||
if ((static_cast<double>(rand()) / RAND_MAX) < err_prob && t.rx_buf != (uint64_t)NULL) {
|
||||
printf("corrupting RX\n");
|
||||
memset((uint8_t*)t.rx_buf, (uint8_t)(rand() % 256), rand() % (t.len+1));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout) {
|
||||
int ret;
|
||||
uint16_t rx_data_len;
|
||||
@@ -316,7 +342,7 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
|
||||
memcpy(tx_buf, &header, sizeof(header));
|
||||
add_checksum(tx_buf, sizeof(header));
|
||||
transfer.len = sizeof(header) + 1;
|
||||
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
|
||||
ret = lltransfer(transfer);
|
||||
if (ret < 0) {
|
||||
LOGE("SPI: failed to send header");
|
||||
goto transfer_fail;
|
||||
@@ -334,7 +360,7 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
|
||||
}
|
||||
add_checksum(tx_buf, tx_len);
|
||||
transfer.len = tx_len + 1;
|
||||
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
|
||||
ret = lltransfer(transfer);
|
||||
if (ret < 0) {
|
||||
LOGE("SPI: failed to send data");
|
||||
goto transfer_fail;
|
||||
@@ -355,7 +381,7 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
|
||||
|
||||
transfer.len = rx_data_len + 1;
|
||||
transfer.rx_buf = (uint64_t)(rx_buf + 2 + 1);
|
||||
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
|
||||
ret = lltransfer(transfer);
|
||||
if (ret < 0) {
|
||||
LOGE("SPI: failed to read rx data");
|
||||
goto transfer_fail;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# functions common among cars
|
||||
from collections import defaultdict, namedtuple
|
||||
from collections import namedtuple
|
||||
from dataclasses import dataclass
|
||||
from enum import IntFlag, ReprEnum, EnumType
|
||||
from dataclasses import replace
|
||||
|
||||
@@ -15,7 +15,6 @@ from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp
|
||||
from openpilot.selfdrive.car.car_helpers import get_car, get_one_can
|
||||
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
|
||||
|
||||
|
||||
REPLAY = "REPLAY" in os.environ
|
||||
|
||||
|
||||
@@ -28,7 +27,7 @@ class CarD:
|
||||
self.sm = messaging.SubMaster(['pandaStates'])
|
||||
self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput'])
|
||||
|
||||
self.can_rcv_timeout_counter = 0 # conseuctive timeout count
|
||||
self.can_rcv_timeout_counter = 0 # consecutive timeout count
|
||||
self.can_rcv_cum_timeout_counter = 0 # cumulative timeout count
|
||||
|
||||
self.CC_prev = car.CarControl.new_message()
|
||||
@@ -54,12 +53,11 @@ class CarD:
|
||||
if not disengage_on_accelerator:
|
||||
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
|
||||
|
||||
car_recognized = self.CP.carName != 'mock'
|
||||
openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle")
|
||||
|
||||
controller_available = self.CI.CC is not None and openpilot_enabled_toggle and not self.CP.dashcamOnly
|
||||
|
||||
self.CP.passive = not car_recognized or not controller_available or self.CP.dashcamOnly
|
||||
self.CP.passive = not controller_available or self.CP.dashcamOnly
|
||||
if self.CP.passive:
|
||||
safety_config = car.CarParams.SafetyConfig.new_message()
|
||||
safety_config.safetyModel = car.CarParams.SafetyModel.noOutput
|
||||
@@ -139,4 +137,3 @@ class CarD:
|
||||
self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=self.CS.canValid))
|
||||
|
||||
self.CC_prev = CC
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ class CarDocs:
|
||||
# min steer & enable speed columns
|
||||
# TODO: set all the min steer speeds in carParams and remove this
|
||||
if self.min_steer_speed is not None:
|
||||
assert CP.minSteerSpeed == 0, f"{CP.carFingerprint}: Minimum steer speed set in both CarDocs and CarParams"
|
||||
assert CP.minSteerSpeed < 0.5, f"{CP.carFingerprint}: Minimum steer speed set in both CarDocs and CarParams"
|
||||
else:
|
||||
self.min_steer_speed = CP.minSteerSpeed
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ FW_VERSIONS = {
|
||||
b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'L1MC-2D053-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'L1MC-2D053-BJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x764, None): [
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
from collections import defaultdict
|
||||
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car.ford.values import get_platform_codes
|
||||
from openpilot.selfdrive.car.ford.fingerprints import FW_VERSIONS
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cars_for_code: defaultdict = defaultdict(lambda: defaultdict(set))
|
||||
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
print(car_model)
|
||||
for ecu in sorted(ecus, key=lambda x: int(x[0])):
|
||||
platform_codes = get_platform_codes(ecus[ecu])
|
||||
for code in platform_codes:
|
||||
cars_for_code[ecu][code].add(car_model)
|
||||
|
||||
print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):')
|
||||
print(f' Codes: {sorted(platform_codes)}')
|
||||
print()
|
||||
|
||||
print('\nCar models vs. platform codes:')
|
||||
for ecu, codes in cars_for_code.items():
|
||||
print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):')
|
||||
for code, cars in codes.items():
|
||||
print(f' {code!r}: {sorted(map(str, cars))}')
|
||||
@@ -1,12 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import unittest
|
||||
from parameterized import parameterized
|
||||
from collections.abc import Iterable
|
||||
|
||||
import capnp
|
||||
from hypothesis import settings, given, strategies as st
|
||||
from parameterized import parameterized
|
||||
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car.ford.values import FW_QUERY_CONFIG
|
||||
from openpilot.selfdrive.car.fw_versions import build_fw_dict
|
||||
from openpilot.selfdrive.car.ford.values import CAR, FW_QUERY_CONFIG, FW_PATTERN, get_platform_codes
|
||||
from openpilot.selfdrive.car.ford.fingerprints import FW_VERSIONS
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
@@ -23,7 +26,7 @@ ECU_ADDRESSES = {
|
||||
}
|
||||
|
||||
|
||||
ECU_FW_CORE = {
|
||||
ECU_PART_NUMBER = {
|
||||
Ecu.eps: [
|
||||
b"14D003",
|
||||
],
|
||||
@@ -37,9 +40,6 @@ ECU_FW_CORE = {
|
||||
b"14F397", # Ford Q3
|
||||
b"14H102", # Ford Q4
|
||||
],
|
||||
Ecu.engine: [
|
||||
b"14C204",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -53,29 +53,96 @@ class TestFordFW(unittest.TestCase):
|
||||
@parameterized.expand(FW_VERSIONS.items())
|
||||
def test_fw_versions(self, car_model: str, fw_versions: dict[tuple[capnp.lib.capnp._EnumModule, int, int | None], Iterable[bytes]]):
|
||||
for (ecu, addr, subaddr), fws in fw_versions.items():
|
||||
self.assertIn(ecu, ECU_FW_CORE, "Unexpected ECU")
|
||||
self.assertIn(ecu, ECU_PART_NUMBER, "Unexpected ECU")
|
||||
self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch")
|
||||
self.assertIsNone(subaddr, "Unexpected ECU subaddress")
|
||||
|
||||
# Software part number takes the form: PREFIX-CORE-SUFFIX
|
||||
# Prefix changes based on the family of part. It includes the model year
|
||||
# and likely the platform.
|
||||
# Core identifies the type of the item (e.g. 14D003 = PSCM, 14C204 = PCM).
|
||||
# Suffix specifies the version of the part. -AA would be followed by -AB.
|
||||
# Small increments in the suffix are usually compatible.
|
||||
# Details: https://forscan.org/forum/viewtopic.php?p=70008#p70008
|
||||
for fw in fws:
|
||||
self.assertEqual(len(fw), 24, "Expected ECU response to be 24 bytes")
|
||||
|
||||
# TODO: parse with regex, don't need detailed error message
|
||||
fw_parts = fw.rstrip(b'\x00').split(b'-')
|
||||
self.assertEqual(len(fw_parts), 3, "Expected FW to be in format: prefix-core-suffix")
|
||||
match = FW_PATTERN.match(fw)
|
||||
self.assertIsNotNone(match, f"Unable to parse FW: {fw!r}")
|
||||
if match:
|
||||
part_number = match.group("part_number")
|
||||
self.assertIn(part_number, ECU_PART_NUMBER[ecu], f"Unexpected part number for {fw!r}")
|
||||
|
||||
prefix, core, suffix = fw_parts
|
||||
self.assertEqual(len(prefix), 4, "Expected FW prefix to be 4 characters")
|
||||
self.assertIn(len(core), (5, 6), "Expected FW core to be 5-6 characters")
|
||||
self.assertIn(core, ECU_FW_CORE[ecu], f"Unexpected FW core for {ecu}")
|
||||
self.assertIn(len(suffix), (2, 3), "Expected FW suffix to be 2-3 characters")
|
||||
codes = get_platform_codes([fw])
|
||||
self.assertEqual(1, len(codes), f"Unable to parse FW: {fw!r}")
|
||||
|
||||
@settings(max_examples=100)
|
||||
@given(data=st.data())
|
||||
def test_platform_codes_fuzzy_fw(self, data):
|
||||
"""Ensure function doesn't raise an exception"""
|
||||
fw_strategy = st.lists(st.binary())
|
||||
fws = data.draw(fw_strategy)
|
||||
get_platform_codes(fws)
|
||||
|
||||
def test_platform_codes_spot_check(self):
|
||||
# Asserts basic platform code parsing behavior for a few cases
|
||||
results = get_platform_codes([
|
||||
b"JX6A-14C204-BPL\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
b"NZ6T-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
b"PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
b"LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
])
|
||||
self.assertEqual(results, {(b"X6A", b"J"), (b"Z6T", b"N"), (b"J6T", b"P"), (b"B5A", b"L")})
|
||||
|
||||
def test_fuzzy_match(self):
|
||||
for platform, fw_by_addr in FW_VERSIONS.items():
|
||||
# Ensure there's no overlaps in platform codes
|
||||
for _ in range(20):
|
||||
car_fw = []
|
||||
for ecu, fw_versions in fw_by_addr.items():
|
||||
ecu_name, addr, sub_addr = ecu
|
||||
fw = random.choice(fw_versions)
|
||||
car_fw.append({"ecu": ecu_name, "fwVersion": fw, "address": addr,
|
||||
"subAddress": 0 if sub_addr is None else sub_addr})
|
||||
|
||||
CP = car.CarParams.new_message(carFw=car_fw)
|
||||
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, FW_VERSIONS)
|
||||
self.assertEqual(matches, {platform})
|
||||
|
||||
def test_match_fw_fuzzy(self):
|
||||
offline_fw = {
|
||||
(Ecu.eps, 0x730, None): [
|
||||
b"L1MC-14D003-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
b"L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
],
|
||||
(Ecu.abs, 0x760, None): [
|
||||
b"L1MC-2D053-BA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
b"L1MC-2D053-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
],
|
||||
(Ecu.fwdRadar, 0x764, None): [
|
||||
b"LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
b"LB5T-14D049-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
],
|
||||
# We consider all model year hints for ECU, even with different platform codes
|
||||
(Ecu.fwdCamera, 0x706, None): [
|
||||
b"LB5T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
b"NC5T-14F397-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
],
|
||||
}
|
||||
expected_fingerprint = CAR.FORD_EXPLORER_MK6
|
||||
|
||||
# ensure that we fuzzy match on all non-exact FW with changed revisions
|
||||
live_fw = {
|
||||
(0x730, None): {b"L1MC-14D003-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
|
||||
(0x760, None): {b"L1MC-2D053-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
|
||||
(0x764, None): {b"LB5T-14D049-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
|
||||
(0x706, None): {b"LB5T-14F397-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
|
||||
}
|
||||
candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw})
|
||||
self.assertEqual(candidates, {expected_fingerprint})
|
||||
|
||||
# model year hint in between the range should match
|
||||
live_fw[(0x706, None)] = {b"MB5T-14F397-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}
|
||||
candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw,})
|
||||
self.assertEqual(candidates, {expected_fingerprint})
|
||||
|
||||
# unseen model year hint should not match
|
||||
live_fw[(0x760, None)] = {b"M1MC-2D053-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}
|
||||
candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw})
|
||||
self.assertEqual(len(candidates), 0, "Should not match new model year hint")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import copy
|
||||
import re
|
||||
from dataclasses import dataclass, field, replace
|
||||
from enum import Enum, IntFlag
|
||||
|
||||
@@ -7,7 +8,7 @@ from cereal import car
|
||||
from openpilot.selfdrive.car import AngleRateLimit, CarSpecs, dbc_dict, DbcDict, PlatformConfig, Platforms
|
||||
from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column, \
|
||||
Device
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, LiveFwVersions, OfflineFwVersions, Request, StdQueries, p16
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
|
||||
@@ -143,6 +144,76 @@ class CAR(Platforms):
|
||||
)
|
||||
|
||||
|
||||
# FW response contains a combined software and part number
|
||||
# A-Z except no I, O or W
|
||||
# e.g. NZ6A-14C204-AAA
|
||||
# 1222-333333-444
|
||||
# 1 = Model year hint (approximates model year/generation)
|
||||
# 2 = Platform hint
|
||||
# 3 = Part number
|
||||
# 4 = Software version
|
||||
FW_ALPHABET = b'A-HJ-NP-VX-Z'
|
||||
FW_PATTERN = re.compile(b'^(?P<model_year_hint>[' + FW_ALPHABET + b'])' +
|
||||
b'(?P<platform_hint>[0-9' + FW_ALPHABET + b']{3})-' +
|
||||
b'(?P<part_number>[0-9' + FW_ALPHABET + b']{5,6})-' +
|
||||
b'(?P<software_revision>[' + FW_ALPHABET + b']{2,})\x00*$')
|
||||
|
||||
|
||||
def get_platform_codes(fw_versions: list[bytes] | set[bytes]) -> set[tuple[bytes, bytes]]:
|
||||
codes = set()
|
||||
for fw in fw_versions:
|
||||
match = FW_PATTERN.match(fw)
|
||||
if match is not None:
|
||||
codes.add((match.group('platform_hint'), match.group('model_year_hint')))
|
||||
|
||||
return codes
|
||||
|
||||
|
||||
def match_fw_to_car_fuzzy(live_fw_versions: LiveFwVersions, vin: str, offline_fw_versions: OfflineFwVersions) -> set[str]:
|
||||
candidates: set[str] = set()
|
||||
|
||||
for candidate, fws in offline_fw_versions.items():
|
||||
# Keep track of ECUs which pass all checks (platform hint, within model year hint range)
|
||||
valid_found_ecus = set()
|
||||
valid_expected_ecus = {ecu[1:] for ecu in fws if ecu[0] in PLATFORM_CODE_ECUS}
|
||||
for ecu, expected_versions in fws.items():
|
||||
addr = ecu[1:]
|
||||
# Only check ECUs expected to have platform codes
|
||||
if ecu[0] not in PLATFORM_CODE_ECUS:
|
||||
continue
|
||||
|
||||
# Expected platform codes & model year hints
|
||||
codes = get_platform_codes(expected_versions)
|
||||
expected_platform_codes = {code for code, _ in codes}
|
||||
expected_model_year_hints = {model_year_hint for _, model_year_hint in codes}
|
||||
|
||||
# Found platform codes & model year hints
|
||||
codes = get_platform_codes(live_fw_versions.get(addr, set()))
|
||||
found_platform_codes = {code for code, _ in codes}
|
||||
found_model_year_hints = {model_year_hint for _, model_year_hint in codes}
|
||||
|
||||
# Check platform code matches for any found versions
|
||||
if not any(found_platform_code in expected_platform_codes for found_platform_code in found_platform_codes):
|
||||
break
|
||||
|
||||
# Check any model year hint within range in the database. Note that some models have more than one
|
||||
# platform code per ECU which we don't consider as separate ranges
|
||||
if not any(min(expected_model_year_hints) <= found_model_year_hint <= max(expected_model_year_hints) for
|
||||
found_model_year_hint in found_model_year_hints):
|
||||
break
|
||||
|
||||
valid_found_ecus.add(addr)
|
||||
|
||||
# If all live ECUs pass all checks for candidate, add it as a match
|
||||
if valid_expected_ecus.issubset(valid_found_ecus):
|
||||
candidates.add(candidate)
|
||||
|
||||
return candidates
|
||||
|
||||
|
||||
# All of these ECUs must be present and are expected to have platform codes we can match
|
||||
PLATFORM_CODE_ECUS = (Ecu.abs, Ecu.fwdCamera, Ecu.fwdRadar, Ecu.eps)
|
||||
|
||||
DATA_IDENTIFIER_FORD_ASBUILT = 0xDE00
|
||||
|
||||
ASBUILT_BLOCKS: list[tuple[int, list]] = [
|
||||
@@ -201,6 +272,8 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
(Ecu.shiftByWire, 0x732, None), # Gear Shift Module
|
||||
(Ecu.debug, 0x7d0, None), # Accessory Protocol Interface Module
|
||||
],
|
||||
# Custom fuzzy fingerprinting function using platform and model year hints
|
||||
match_fw_to_car_fuzzy=match_fw_to_car_fuzzy,
|
||||
)
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
@@ -309,6 +309,7 @@ FW_VERSIONS = {
|
||||
b'37805-5BB-L540\x00\x00',
|
||||
b'37805-5BB-L630\x00\x00',
|
||||
b'37805-5BB-L640\x00\x00',
|
||||
b'37805-5BF-J130\x00\x00',
|
||||
],
|
||||
(Ecu.transmission, 0x18da1ef1, None): [
|
||||
b'28101-5CG-A920\x00\x00',
|
||||
@@ -342,6 +343,7 @@ FW_VERSIONS = {
|
||||
b'57114-TGG-G320\x00\x00',
|
||||
b'57114-TGG-L320\x00\x00',
|
||||
b'57114-TGG-L330\x00\x00',
|
||||
b'57114-TGH-L130\x00\x00',
|
||||
b'57114-TGK-T320\x00\x00',
|
||||
b'57114-TGL-G330\x00\x00',
|
||||
],
|
||||
@@ -355,6 +357,7 @@ FW_VERSIONS = {
|
||||
b'39990-TGG-A020\x00\x00',
|
||||
b'39990-TGG-A120\x00\x00',
|
||||
b'39990-TGG-J510\x00\x00',
|
||||
b'39990-TGH-J530\x00\x00',
|
||||
b'39990-TGL-E130\x00\x00',
|
||||
b'39990-TGN-E120\x00\x00',
|
||||
],
|
||||
@@ -369,6 +372,7 @@ FW_VERSIONS = {
|
||||
b'77959-TGG-G110\x00\x00',
|
||||
b'77959-TGG-J320\x00\x00',
|
||||
b'77959-TGG-Z820\x00\x00',
|
||||
b'77959-TGH-J110\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x18dab0f1, None): [
|
||||
b'36802-TBA-A150\x00\x00',
|
||||
@@ -380,6 +384,7 @@ FW_VERSIONS = {
|
||||
b'36802-TGG-A130\x00\x00',
|
||||
b'36802-TGG-G040\x00\x00',
|
||||
b'36802-TGG-G130\x00\x00',
|
||||
b'36802-TGH-A140\x00\x00',
|
||||
b'36802-TGK-Q120\x00\x00',
|
||||
b'36802-TGL-G040\x00\x00',
|
||||
],
|
||||
@@ -394,6 +399,7 @@ FW_VERSIONS = {
|
||||
b'36161-TGG-G070\x00\x00',
|
||||
b'36161-TGG-G130\x00\x00',
|
||||
b'36161-TGG-G140\x00\x00',
|
||||
b'36161-TGH-A140\x00\x00',
|
||||
b'36161-TGK-Q120\x00\x00',
|
||||
b'36161-TGL-G050\x00\x00',
|
||||
b'36161-TGL-G070\x00\x00',
|
||||
@@ -401,6 +407,7 @@ FW_VERSIONS = {
|
||||
(Ecu.gateway, 0x18daeff1, None): [
|
||||
b'38897-TBA-A020\x00\x00',
|
||||
b'38897-TBA-A110\x00\x00',
|
||||
b'38897-TGH-A010\x00\x00',
|
||||
],
|
||||
(Ecu.electricBrakeBooster, 0x18da2bf1, None): [
|
||||
b'39494-TGL-G030\x00\x00',
|
||||
|
||||
@@ -284,6 +284,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1500 ',
|
||||
b'\xf1\x00TM__ SCC F-CUP 1.00 1.01 99110-S1500 ',
|
||||
b'\xf1\x00TM__ SCC FHCUP 1.00 1.00 99110-S1500 ',
|
||||
b'\xf1\x00TM__ SCC FHCUP 1.00 1.01 99110-S1500 ',
|
||||
],
|
||||
(Ecu.abs, 0x7d1, None): [
|
||||
b'\xf1\x00TM ESC \x01 102!\x04\x03 58910-S2DA0',
|
||||
@@ -294,6 +295,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00TM ESC \x03 102!\x04\x03 58910-S2DA0',
|
||||
b'\xf1\x00TM ESC \x04 101 \x08\x04 58910-S2GA0',
|
||||
b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0',
|
||||
b'\xf1\x00TM ESC \x04 103"\x07\x08 58910-S2GA0',
|
||||
b'\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0',
|
||||
b'\xf1\x00TM ESC 103!\x030 58910-S1MA0',
|
||||
],
|
||||
@@ -415,6 +417,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00LX2_ SCC FHCUP 1.00 1.03 99110-S8100 ',
|
||||
b'\xf1\x00LX2_ SCC FHCUP 1.00 1.04 99110-S8100 ',
|
||||
b'\xf1\x00LX2_ SCC FHCUP 1.00 1.05 99110-S8100 ',
|
||||
b'\xf1\x00ON__ FCA FHCUP 1.00 1.00 99110-S9110 ',
|
||||
b'\xf1\x00ON__ FCA FHCUP 1.00 1.01 99110-S9110 ',
|
||||
b'\xf1\x00ON__ FCA FHCUP 1.00 1.02 99110-S9100 ',
|
||||
b'\xf1\x00ON__ FCA FHCUP 1.00 1.03 99110-S9100 ',
|
||||
@@ -868,6 +871,7 @@ FW_VERSIONS = {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819',
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.01 99210-AB000 210205',
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.02 99210-AB000 220111',
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AA000 200819',
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AB000 220426',
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.06 99210-AA000 220111',
|
||||
@@ -1114,6 +1118,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00KA4 MFC AT EUR LHD 1.00 1.06 99210-R0000 220221',
|
||||
b'\xf1\x00KA4 MFC AT KOR LHD 1.00 1.06 99210-R0000 220221',
|
||||
b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.00 99210-R0100 230105',
|
||||
b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.01 99210-R0100 230710',
|
||||
b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.05 99210-R0000 201221',
|
||||
b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.06 99210-R0000 220221',
|
||||
b'\xf1\x00KA4CMFC AT CHN LHD 1.00 1.01 99211-I4000 210525',
|
||||
@@ -1128,6 +1133,7 @@ FW_VERSIONS = {
|
||||
CAR.KIA_K8_HEV_1ST_GEN: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00GL3HMFC AT KOR LHD 1.00 1.03 99211-L8000 210907',
|
||||
b'\xf1\x00GL3HMFC AT KOR LHD 1.00 1.04 99211-L8000 230207',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00GL3_ RDR ----- 1.00 1.02 99110-L8000 ',
|
||||
|
||||
@@ -795,6 +795,3 @@ NON_SCC_FCA_CAR = CAR.with_sp_flags(HyundaiFlagsSP.SP_NON_SCC_FCA)
|
||||
NON_SCC_RADAR_FCA_CAR = CAR.with_sp_flags(HyundaiFlagsSP.SP_NON_SCC_RADAR_FCA)
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
if __name__ == "__main__":
|
||||
CAR.print_debug(HyundaiFlags)
|
||||
|
||||
@@ -18,6 +18,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.wheelbase = 2.70
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.steerRatio = 13.
|
||||
ret.dashcamOnly = True
|
||||
return ret
|
||||
|
||||
def _update(self, c):
|
||||
|
||||
@@ -276,6 +276,3 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
)
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
if __name__ == "__main__":
|
||||
CAR.print_debug(SubaruFlags)
|
||||
|
||||
@@ -94,7 +94,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
car_fw = msg.carParams.carFw
|
||||
if msg.carParams.openpilotLongitudinalControl:
|
||||
experimental_long = True
|
||||
if cls.platform is None and not cls.ci:
|
||||
if cls.platform is None and not cls.test_route_on_bucket:
|
||||
live_fingerprint = msg.carParams.carFingerprint
|
||||
cls.platform = MIGRATION.get(live_fingerprint, live_fingerprint)
|
||||
|
||||
|
||||
@@ -891,6 +891,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x8704L906056CR\xf1\x892181',
|
||||
b'\xf1\x8704L906056CR\xf1\x892797',
|
||||
b'\xf1\x8705E906018AS\xf1\x899596',
|
||||
b'\xf1\x8781A906259B \xf1\x890003',
|
||||
b'\xf1\x878V0906264H \xf1\x890005',
|
||||
b'\xf1\x878V0907115E \xf1\x890002',
|
||||
],
|
||||
@@ -900,6 +901,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x870CW300050J \xf1\x891908',
|
||||
b'\xf1\x870D9300014S \xf1\x895202',
|
||||
b'\xf1\x870D9300042M \xf1\x895016',
|
||||
b'\xf1\x870GC300014P \xf1\x892801',
|
||||
b'\xf1\x870GC300043A \xf1\x892304',
|
||||
],
|
||||
(Ecu.srs, 0x715, None): [
|
||||
@@ -910,6 +912,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1312001313001305171311052900',
|
||||
b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\x0e1312001313001305171311052900',
|
||||
b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\x0e1312001313001305171311052900',
|
||||
b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100110200--1113121149',
|
||||
],
|
||||
(Ecu.eps, 0x712, None): [
|
||||
b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571N60511A1',
|
||||
@@ -918,8 +921,10 @@ FW_VERSIONS = {
|
||||
b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0511N01805A0',
|
||||
b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521N01309A1',
|
||||
b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521N05808A1',
|
||||
b'\xf1\x875WA907145M \xf1\x891051\xf1\x82\x0013N619137N',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x872Q0907572AA\xf1\x890396',
|
||||
b'\xf1\x872Q0907572M \xf1\x890233',
|
||||
b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\x0101',
|
||||
b'\xf1\x875Q0907572H \xf1\x890620',
|
||||
|
||||
@@ -2,7 +2,7 @@ from cereal import car
|
||||
from panda import Panda
|
||||
from openpilot.selfdrive.car import get_safety_config
|
||||
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
|
||||
from openpilot.selfdrive.car.volkswagen.values import CAR, CANBUS, NetworkLocation, TransmissionType, GearShifter, VolkswagenFlags
|
||||
from openpilot.selfdrive.car.volkswagen.values import CAR, CANBUS, CarControllerParams, NetworkLocation, TransmissionType, GearShifter, VolkswagenFlags
|
||||
|
||||
ButtonType = car.CarState.ButtonEvent.Type
|
||||
EventName = car.CarEvent.EventName
|
||||
@@ -112,7 +112,7 @@ class CarInterface(CarInterfaceBase):
|
||||
enable_buttons=(ButtonType.setCruise, ButtonType.resumeCruise))
|
||||
|
||||
# Low speed steer alert hysteresis logic
|
||||
if self.CP.minSteerSpeed > 0. and ret.vEgo < (self.CP.minSteerSpeed + 1.):
|
||||
if (self.CP.minSteerSpeed - 1e-3) > CarControllerParams.DEFAULT_MIN_STEER_SPEED and ret.vEgo < (self.CP.minSteerSpeed + 1.):
|
||||
self.low_speed_alert = True
|
||||
elif ret.vEgo > (self.CP.minSteerSpeed + 2.):
|
||||
self.low_speed_alert = False
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import re
|
||||
import unittest
|
||||
|
||||
@@ -38,9 +39,9 @@ class TestVolkswagenPlatformConfigs(unittest.TestCase):
|
||||
f"Shared chassis codes: {comp}")
|
||||
|
||||
def test_custom_fuzzy_fingerprinting(self):
|
||||
for platform in CAR:
|
||||
expected_radar_fw = FW_VERSIONS[platform][Ecu.fwdRadar, 0x757, None]
|
||||
all_radar_fw = list({fw for ecus in FW_VERSIONS.values() for fw in ecus[Ecu.fwdRadar, 0x757, None]})
|
||||
|
||||
for platform in CAR:
|
||||
with self.subTest(platform=platform):
|
||||
for wmi in WMI:
|
||||
for chassis_code in platform.config.chassis_codes | {"00"}:
|
||||
@@ -50,9 +51,9 @@ class TestVolkswagenPlatformConfigs(unittest.TestCase):
|
||||
vin = "".join(vin)
|
||||
|
||||
# Check a few FW cases - expected, unexpected
|
||||
for radar_fw in expected_radar_fw + [b'\xf1\x877H9907572AA\xf1\x890396']:
|
||||
for radar_fw in random.sample(all_radar_fw, 5) + [b'\xf1\x875Q0907572G \xf1\x890571', b'\xf1\x877H9907572AA\xf1\x890396']:
|
||||
should_match = ((wmi in platform.config.wmis and chassis_code in platform.config.chassis_codes) and
|
||||
radar_fw in expected_radar_fw)
|
||||
radar_fw in all_radar_fw)
|
||||
|
||||
live_fws = {(0x757, None): [radar_fw]}
|
||||
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fws, vin, FW_VERSIONS)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from collections import namedtuple
|
||||
from collections import defaultdict, namedtuple
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum, IntFlag, StrEnum
|
||||
|
||||
@@ -9,7 +9,7 @@ from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.selfdrive.car import dbc_dict, CarSpecs, DbcDict, PlatformConfig, Platforms
|
||||
from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column, \
|
||||
Device
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16
|
||||
from openpilot.selfdrive.car.fw_query_definitions import EcuAddrSubAddr, FwQueryConfig, Request, p16
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
NetworkLocation = car.CarParams.NetworkLocation
|
||||
@@ -34,6 +34,8 @@ class CarControllerParams:
|
||||
STEER_TIME_ALERT = STEER_TIME_MAX - 10 # If mitigation fails, time to soft disengage before EPS timer expires
|
||||
STEER_TIME_STUCK_TORQUE = 1.9 # EPS limits same torque to 6 seconds, reset timer 3x within that period
|
||||
|
||||
DEFAULT_MIN_STEER_SPEED = 0.4 # m/s, newer EPS racks fault below this speed, don't show a low speed alert
|
||||
|
||||
ACCEL_MAX = 2.0 # 2.0 m/s max acceleration
|
||||
ACCEL_MIN = -3.5 # 3.5 m/s max deceleration
|
||||
|
||||
@@ -159,6 +161,7 @@ class VolkswagenPQPlatformConfig(VolkswagenMQBPlatformConfig):
|
||||
class VolkswagenCarSpecs(CarSpecs):
|
||||
centerToFrontRatio: float = 0.45
|
||||
steerRatio: float = 15.6
|
||||
minSteerSpeed: float = CarControllerParams.DEFAULT_MIN_STEER_SPEED
|
||||
|
||||
|
||||
class Footnote(Enum):
|
||||
@@ -195,6 +198,9 @@ class VWCarDocs(CarDocs):
|
||||
if CP.carFingerprint in (CAR.VOLKSWAGEN_CRAFTER_MK2, CAR.VOLKSWAGEN_TRANSPORTER_T61):
|
||||
self.car_parts = CarParts([Device.threex_angled_mount, CarHarness.j533])
|
||||
|
||||
if abs(CP.minSteerSpeed - CarControllerParams.DEFAULT_MIN_STEER_SPEED) < 1e-3:
|
||||
self.min_steer_speed = 0
|
||||
|
||||
|
||||
# Check the 7th and 8th characters of the VIN before adding a new CAR. If the
|
||||
# chassis code is already listed below, don't add a new CAR, just add to the
|
||||
@@ -372,7 +378,8 @@ class CAR(Platforms):
|
||||
)
|
||||
SEAT_ATECA_MK1 = VolkswagenMQBPlatformConfig(
|
||||
[
|
||||
VWCarDocs("SEAT Ateca 2018"),
|
||||
VWCarDocs("CUPRA Ateca 2018-23"),
|
||||
VWCarDocs("SEAT Ateca 2016-23"),
|
||||
VWCarDocs("SEAT Leon 2014-20"),
|
||||
],
|
||||
VolkswagenCarSpecs(mass=1300, wheelbase=2.64),
|
||||
@@ -427,19 +434,26 @@ class CAR(Platforms):
|
||||
def match_fw_to_car_fuzzy(live_fw_versions, vin, offline_fw_versions) -> set[str]:
|
||||
candidates = set()
|
||||
|
||||
# Compile all FW versions for each ECU
|
||||
all_ecu_versions: dict[EcuAddrSubAddr, set[str]] = defaultdict(set)
|
||||
for ecus in offline_fw_versions.values():
|
||||
for ecu, versions in ecus.items():
|
||||
all_ecu_versions[ecu] |= set(versions)
|
||||
|
||||
# Check the WMI and chassis code to determine the platform
|
||||
wmi = vin[:3]
|
||||
chassis_code = vin[6:8]
|
||||
|
||||
for platform in CAR:
|
||||
valid_ecus = set()
|
||||
for ecu, expected_versions in offline_fw_versions[platform].items():
|
||||
for ecu in offline_fw_versions[platform]:
|
||||
addr = ecu[1:]
|
||||
if ecu[0] not in CHECK_FUZZY_ECUS:
|
||||
continue
|
||||
|
||||
# Sanity check that a subset of Volkswagen FW is in the database
|
||||
# Sanity check that live FW is in the superset of all FW, Volkswagen ECU part numbers are commonly shared
|
||||
found_versions = live_fw_versions.get(addr, [])
|
||||
expected_versions = all_ecu_versions[ecu]
|
||||
if not any(found_version in expected_versions for found_version in found_versions):
|
||||
break
|
||||
|
||||
|
||||
@@ -111,7 +111,6 @@ class Controls:
|
||||
if not self.CP.openpilotLongitudinalControl:
|
||||
self.params.remove("ExperimentalMode")
|
||||
|
||||
self.CC = car.CarControl.new_message()
|
||||
self.CS_prev = car.CarState.new_message()
|
||||
self.AM = AlertManager()
|
||||
self.events = Events()
|
||||
@@ -812,9 +811,6 @@ class Controls:
|
||||
cc_send.carControl = CC
|
||||
self.pm.send('carControl', cc_send)
|
||||
|
||||
# copy CarControl to pass to CarInterface on the next iteration
|
||||
self.CC = CC
|
||||
|
||||
def step(self):
|
||||
start_time = time.monotonic()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Offroad_TemperatureTooHigh": {
|
||||
"text": "Device temperature too high. System cooling down before starting. Current internal component temperature: %1",
|
||||
"severity": 1
|
||||
"severity": 0
|
||||
},
|
||||
"Offroad_ConnectivityNeededPrompt": {
|
||||
"text": "Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1",
|
||||
@@ -17,10 +17,6 @@
|
||||
"severity": 1,
|
||||
"_comment": "Set extra field to the failed reason."
|
||||
},
|
||||
"Offroad_InvalidTime": {
|
||||
"text": "Invalid date and time settings, system won't start. Connect to internet to set time.",
|
||||
"severity": 1
|
||||
},
|
||||
"Offroad_IsTakingSnapshot": {
|
||||
"text": "Taking camera snapshots. System won't start until finished.",
|
||||
"severity": 0
|
||||
|
||||
@@ -21,5 +21,5 @@ if __name__ == "__main__":
|
||||
|
||||
if len(ts[s]) > 2:
|
||||
d = np.diff(ts[s])
|
||||
print(f"{s:25} {np.mean(d):.2f} {np.std(d):.2f} {np.max(d):.2f} {np.min(d):.2f}")
|
||||
print(f"{s:25} {np.mean(d):7.2f} {np.std(d):7.2f} {np.max(d):7.2f} {np.min(d):7.2f}")
|
||||
time.sleep(1)
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
from openpilot.selfdrive.car.values import BRANDS
|
||||
|
||||
for brand in BRANDS:
|
||||
all_flags = set()
|
||||
for platform in brand:
|
||||
if platform.config.flags != 0:
|
||||
all_flags |= set(platform.config.flags)
|
||||
|
||||
if len(all_flags):
|
||||
print(brand.__module__.split('.')[-2].upper() + ':')
|
||||
for flag in sorted(all_flags):
|
||||
print(f' {flag.name:<24}:', {platform.name for platform in brand.with_flags(flag)})
|
||||
print()
|
||||
@@ -26,7 +26,7 @@ def dmonitoringd_thread():
|
||||
steering_wheel_engaged = False
|
||||
hands_on_wheel_monitoring_enabled = params.get_bool("HandsOnWheelMonitoring")
|
||||
|
||||
# 10Hz <- dmonitoringmodeld
|
||||
# 20Hz <- dmonitoringmodeld
|
||||
while True:
|
||||
sm.update()
|
||||
if not sm.updated['driverStateV2']:
|
||||
@@ -53,14 +53,15 @@ def dmonitoringd_thread():
|
||||
if sm.all_checks() and len(sm['liveCalibration'].rpyCalib):
|
||||
driver_status.update_states(sm['driverStateV2'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled)
|
||||
|
||||
# Block engaging after max number of distrations
|
||||
# Block engaging after max number of distrations or when alert active
|
||||
if driver_status.terminal_alert_cnt >= driver_status.settings._MAX_TERMINAL_ALERTS or \
|
||||
driver_status.terminal_time >= driver_status.settings._MAX_TERMINAL_DURATION:
|
||||
driver_status.terminal_time >= driver_status.settings._MAX_TERMINAL_DURATION or \
|
||||
driver_status.always_on and driver_status.awareness <= driver_status.threshold_prompt:
|
||||
events.add(car.CarEvent.EventName.tooDistracted)
|
||||
|
||||
# Update events from driver state
|
||||
driver_status.update_events(events, driver_engaged, sm['controlsState'].enabled,
|
||||
sm['carState'].standstill, sm['carState'].gearShifter in [car.CarState.GearShifter.reverse, car.CarState.GearShifter.park])
|
||||
sm['carState'].standstill, sm['carState'].gearShifter in [car.CarState.GearShifter.reverse, car.CarState.GearShifter.park], sm['carState'].vEgo)
|
||||
# Update events and state from hands on wheel monitoring status
|
||||
if hands_on_wheel_monitoring_enabled:
|
||||
hands_on_wheel_status.update(events, steering_wheel_engaged, sm['controlsState'].enabled, sm['carState'].vEgo)
|
||||
|
||||
@@ -55,6 +55,7 @@ class DRIVER_MONITOR_SETTINGS():
|
||||
self._POSESTD_THRESHOLD = 0.3
|
||||
self._HI_STD_FALLBACK_TIME = int(10 / self._DT_DMON) # fall back to wheel touch if model is uncertain for 10s
|
||||
self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz
|
||||
self._ALWAYS_ON_ALERT_MIN_SPEED = 7
|
||||
|
||||
self._POSE_CALIB_MIN_SPEED = 13 # 30 mph
|
||||
self._POSE_OFFSET_MIN_COUNT = int(60 / self._DT_DMON) # valid data counts before calibration completes, 1min cumulative
|
||||
@@ -302,7 +303,7 @@ class DriverStatus():
|
||||
elif self.face_detected and self.pose.low_std:
|
||||
self.hi_stds = 0
|
||||
|
||||
def update_events(self, events, driver_engaged, ctrl_active, standstill, wrong_gear):
|
||||
def update_events(self, events, driver_engaged, ctrl_active, standstill, wrong_gear, car_speed):
|
||||
always_on_valid = self.always_on and not wrong_gear
|
||||
if (driver_engaged and self.awareness > 0 and not self.active_monitoring_mode) or \
|
||||
(not always_on_valid and not ctrl_active) or \
|
||||
@@ -327,14 +328,19 @@ class DriverStatus():
|
||||
if self.awareness > self.threshold_prompt:
|
||||
return
|
||||
|
||||
standstill_exemption = standstill and self.awareness - self.step_change <= self.threshold_prompt
|
||||
always_on_red_exemption = always_on_valid and not ctrl_active and self.awareness - self.step_change <= 0
|
||||
_reaching_audible = self.awareness - self.step_change <= self.threshold_prompt
|
||||
_reaching_terminal = self.awareness - self.step_change <= 0
|
||||
standstill_exemption = standstill and _reaching_audible
|
||||
always_on_red_exemption = always_on_valid and not ctrl_active and _reaching_terminal
|
||||
always_on_lowspeed_exemption = always_on_valid and not ctrl_active and car_speed < self.settings._ALWAYS_ON_ALERT_MIN_SPEED and _reaching_audible
|
||||
|
||||
certainly_distracted = self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected
|
||||
maybe_distracted = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME or not self.face_detected
|
||||
|
||||
if certainly_distracted or maybe_distracted:
|
||||
# should always be counting if distracted unless at standstill and reaching orange
|
||||
# should always be counting if distracted unless at standstill (lowspeed for always-on) and reaching orange
|
||||
# also will not be reaching 0 if DM is active when not engaged
|
||||
if not standstill_exemption and not always_on_red_exemption:
|
||||
if not (standstill_exemption or always_on_red_exemption or always_on_lowspeed_exemption):
|
||||
self.awareness = max(self.awareness - self.step_change, -0.1)
|
||||
|
||||
alert = None
|
||||
|
||||
@@ -63,7 +63,7 @@ class TestMonitoring(unittest.TestCase):
|
||||
# cal_rpy and car_speed don't matter here
|
||||
|
||||
# evaluate events at 10Hz for tests
|
||||
DS.update_events(e, interaction[idx], engaged[idx], standstill[idx], 0)
|
||||
DS.update_events(e, interaction[idx], engaged[idx], standstill[idx], 0, 0)
|
||||
events.append(e)
|
||||
assert len(events) == len(msgs), f"got {len(events)} for {len(msgs)} driverState input msgs"
|
||||
return events, DS
|
||||
|
||||
@@ -1 +1 @@
|
||||
692a21e4a722d91086998b532ca6759a3f85c345
|
||||
685a2bb9aacdd790e26d14aa49d3792c3ed65125
|
||||
@@ -27,8 +27,15 @@ from openpilot.selfdrive.test.helpers import set_params_enabled, release_only
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from openpilot.tools.lib.logreader import LogReader
|
||||
|
||||
# Baseline CPU usage by process
|
||||
"""
|
||||
CPU usage budget
|
||||
* each process is entitled to at least 8%
|
||||
* total CPU usage of openpilot (sum(PROCS.values())
|
||||
should not exceed MAX_TOTAL_CPU
|
||||
"""
|
||||
MAX_TOTAL_CPU = 250. # total for all 8 cores
|
||||
PROCS = {
|
||||
# Baseline CPU usage by process
|
||||
"selfdrive.controls.controlsd": 46.0,
|
||||
"./loggerd": 14.0,
|
||||
"./encoderd": 17.0,
|
||||
@@ -274,6 +281,7 @@ class TestOnroad(unittest.TestCase):
|
||||
result += f"{proc_name.ljust(35)} {cpu_usage:5.2f}% ({exp}%) {err}\n"
|
||||
if len(err) > 0:
|
||||
cpu_ok = False
|
||||
result += "------------------------------------------------\n"
|
||||
|
||||
# Ensure there's no missing procs
|
||||
all_procs = {p.name for p in self.service_msgs['managerState'][0].managerState.processes if p.shouldBeRunning}
|
||||
@@ -281,7 +289,14 @@ class TestOnroad(unittest.TestCase):
|
||||
with self.subTest(proc=p):
|
||||
assert any(p in pp for pp in PROCS.keys()), f"Expected CPU usage missing for {p}"
|
||||
|
||||
result += "------------------------------------------------\n"
|
||||
# total CPU check
|
||||
procs_tot = sum([(max(x) if isinstance(x, tuple) else x) for x in PROCS.values()])
|
||||
with self.subTest(name="total CPU"):
|
||||
assert procs_tot < MAX_TOTAL_CPU, "Total CPU budget exceeded"
|
||||
result += "------------------------------------------------\n"
|
||||
result += f"Total allocated CPU usage is {procs_tot}%, budget is {MAX_TOTAL_CPU}%, {MAX_TOTAL_CPU-procs_tot:.1f}% left\n"
|
||||
result += "------------------------------------------------\n"
|
||||
|
||||
print(result)
|
||||
|
||||
self.assertTrue(cpu_ok)
|
||||
|
||||
@@ -440,10 +440,6 @@
|
||||
<translation>غير قادر على تحميل التحديثات
|
||||
%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid date and time settings, system won't start. Connect to internet to set time.</source>
|
||||
<translation>إعدادات التاريخ والتوقيت غير صحيحة، لن يبدأ النظام. اتصل بالإنترنت من أجل ضبط الوقت.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Taking camera snapshots. System won't start until finished.</source>
|
||||
<translation>التقاط لقطات كاميرا. لن يبدأ النظام حتى تنتهي هذه العملية.</translation>
|
||||
|
||||
@@ -431,10 +431,6 @@
|
||||
%1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid date and time settings, system won't start. Connect to internet to set time.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Taking camera snapshots. System won't start until finished.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
|
||||
@@ -436,10 +436,6 @@
|
||||
<translation>Impossible de télécharger les mises à jour
|
||||
%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid date and time settings, system won't start. Connect to internet to set time.</source>
|
||||
<translation>Paramètres de date et d'heure invalides, le système ne démarrera pas. Connectez l'appareil à Internet pour régler l'heure.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Taking camera snapshots. System won't start until finished.</source>
|
||||
<translation>Capture de clichés photo. Le système ne démarrera pas tant qu'il n'est pas terminé.</translation>
|
||||
|
||||
@@ -430,10 +430,6 @@
|
||||
%1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid date and time settings, system won't start. Connect to internet to set time.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Taking camera snapshots. System won't start until finished.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
|
||||
@@ -431,10 +431,6 @@
|
||||
<translation>업데이트를 다운로드할 수 없습니다
|
||||
%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid date and time settings, system won't start. Connect to internet to set time.</source>
|
||||
<translation>날짜 및 시간 설정이 잘못되어 시스템이 시작되지 않습니다. 날짜와 시간을 동기화하려면 인터넷에 연결하세요.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Taking camera snapshots. System won't start until finished.</source>
|
||||
<translation>카메라 스냅샷 찍기가 완료될 때까지 시스템이 시작되지 않습니다.</translation>
|
||||
|
||||
@@ -432,10 +432,6 @@
|
||||
<translation>Não é possível baixar atualizações
|
||||
%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid date and time settings, system won't start. Connect to internet to set time.</source>
|
||||
<translation>Configurações de data e hora inválidas, o sistema não será iniciado. Conecte-se à internet para definir o horário.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Taking camera snapshots. System won't start until finished.</source>
|
||||
<translation>Tirando fotos da câmera. O sistema não será iniciado até terminar.</translation>
|
||||
|
||||
@@ -435,10 +435,6 @@
|
||||
<translation>ไม่สามารถดาวน์โหลดอัพเดทได้
|
||||
%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid date and time settings, system won't start. Connect to internet to set time.</source>
|
||||
<translation>วันที่และเวลาไม่ถูกต้อง ระบบจะไม่เริ่มทำงาน เชื่อต่ออินเตอร์เน็ตเพื่อตั้งเวลา</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Taking camera snapshots. System won't start until finished.</source>
|
||||
<translation>กล้องกำลังถ่ายภาพ ระบบจะไม่เริ่มทำงานจนกว่าจะเสร็จ</translation>
|
||||
|
||||
@@ -434,10 +434,6 @@
|
||||
%1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid date and time settings, system won't start. Connect to internet to set time.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Taking camera snapshots. System won't start until finished.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
|
||||
@@ -431,10 +431,6 @@
|
||||
<translation>无法下载更新
|
||||
%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid date and time settings, system won't start. Connect to internet to set time.</source>
|
||||
<translation>日期和时间设置无效,系统无法启动。请连接至互联网以设置时间。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Taking camera snapshots. System won't start until finished.</source>
|
||||
<translation>正在使用相机拍摄中。在完成之前,系统将无法启动。</translation>
|
||||
|
||||
@@ -431,10 +431,6 @@
|
||||
<translation>無法下載更新
|
||||
%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid date and time settings, system won't start. Connect to internet to set time.</source>
|
||||
<translation>日期和時間設定無效,系統無法啟動。請連接至網際網路以設定時間。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Taking camera snapshots. System won't start until finished.</source>
|
||||
<translation>正在使用相機拍攝中。在完成之前,系統將無法啟動。</translation>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[
|
||||
{
|
||||
"name": "boot",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/boot-543fdc8aadf700f33a6e90740b8a227036bbd190626861d45ba1eb0d9ac422d1.img.xz",
|
||||
"hash": "543fdc8aadf700f33a6e90740b8a227036bbd190626861d45ba1eb0d9ac422d1",
|
||||
"hash_raw": "543fdc8aadf700f33a6e90740b8a227036bbd190626861d45ba1eb0d9ac422d1",
|
||||
"size": 15636480,
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/boot-5674ea6767e7198cf1e7def3de66a57061f001ed76d43dc4b4f84de545c53c6f.img.xz",
|
||||
"hash": "5674ea6767e7198cf1e7def3de66a57061f001ed76d43dc4b4f84de545c53c6f",
|
||||
"hash_raw": "5674ea6767e7198cf1e7def3de66a57061f001ed76d43dc4b4f84de545c53c6f",
|
||||
"size": 16029696,
|
||||
"sparse": false,
|
||||
"full_check": true,
|
||||
"has_ab": true
|
||||
@@ -61,17 +61,17 @@
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-bd2967074298a2686f81e2094db3867d7cb2605750d63b1a7309a923f6985b2a.img.xz",
|
||||
"hash": "dc2f960631f02446d912885786922c27be4eb1ec6c202cacce6699d5a74021ba",
|
||||
"hash_raw": "bd2967074298a2686f81e2094db3867d7cb2605750d63b1a7309a923f6985b2a",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-1badfe72851628d6cf9200a53a6151bb4e797b49c717141409fc57138eae388a.img.xz",
|
||||
"hash": "328e90c62068222dfd98f71dd3f6251fcb962f082b49c6be66ab2699f5db6f4f",
|
||||
"hash_raw": "1badfe72851628d6cf9200a53a6151bb4e797b49c717141409fc57138eae388a",
|
||||
"size": 10737418240,
|
||||
"sparse": true,
|
||||
"full_check": false,
|
||||
"has_ab": true,
|
||||
"alt": {
|
||||
"hash": "283e5e754593c6e1bb5e9d63b54624cda5475b88bc1b130fe6ab13acdbd966e2",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-bd2967074298a2686f81e2094db3867d7cb2605750d63b1a7309a923f6985b2a.img.xz",
|
||||
"size": 4548070712
|
||||
"hash": "bc11d2148f29862ee1326aca2af1cf6bbf5fed831e3f8f6b8f7a0f110dfe8d26",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-1badfe72851628d6cf9200a53a6151bb4e797b49c717141409fc57138eae388a.img.xz",
|
||||
"size": 4548070000
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -26,7 +26,7 @@ cabana_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET")
|
||||
cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "assets/assets.o"]))
|
||||
|
||||
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcanstream.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc',
|
||||
'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
|
||||
'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
|
||||
'utils/export.cc', 'utils/util.cc',
|
||||
'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc',
|
||||
'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
|
||||
+10
-22
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "tools/cabana/mainwin.h"
|
||||
#include "tools/cabana/streamselector.h"
|
||||
#include "tools/cabana/streams/devicestream.h"
|
||||
#include "tools/cabana/streams/pandastream.h"
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
@@ -82,28 +81,17 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
{
|
||||
MainWindow w;
|
||||
QTimer::singleShot(0, [&]() {
|
||||
if (!stream) {
|
||||
StreamSelector dlg(&stream);
|
||||
dlg.exec();
|
||||
dbc_file = dlg.dbcFile();
|
||||
}
|
||||
if (!stream) {
|
||||
stream = new DummyStream(&app);
|
||||
}
|
||||
stream->start();
|
||||
if (!dbc_file.isEmpty()) {
|
||||
w.loadFile(dbc_file);
|
||||
}
|
||||
w.show();
|
||||
});
|
||||
|
||||
ret = app.exec();
|
||||
MainWindow w;
|
||||
if (stream) {
|
||||
stream->start();
|
||||
if (!dbc_file.isEmpty()) {
|
||||
w.loadFile(dbc_file);
|
||||
}
|
||||
} else {
|
||||
w.openStream();
|
||||
}
|
||||
|
||||
w.show();
|
||||
int ret = app.exec();
|
||||
delete can;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
// ChartAxisElement's padding is 4 (https://codebrowser.dev/qt5/qtcharts/src/charts/axis/chartaxiselement_p.h.html)
|
||||
const int AXIS_X_TOP_MARGIN = 4;
|
||||
const double MIN_ZOOM_SECONDS = 0.01; // 10ms
|
||||
// Define a small value of epsilon to compare double values
|
||||
const float EPSILON = 0.000001;
|
||||
static inline bool xLessThan(const QPointF &p, float x) { return p.x() < (x - EPSILON); }
|
||||
@@ -511,7 +512,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
|
||||
if (rubber->width() <= 0) {
|
||||
// no rubber dragged, seek to mouse position
|
||||
can->seekTo(min);
|
||||
} else if (rubber->width() > 10 && (max - min) > 0.01) { // Minimum range is 10 milliseconds.
|
||||
} else if (rubber->width() > 10 && (max - min) > MIN_ZOOM_SECONDS) {
|
||||
charts_widget->zoom_undo_stack->push(new ZoomCommand(charts_widget, {min, max}));
|
||||
} else {
|
||||
viewport()->update();
|
||||
|
||||
@@ -197,7 +197,8 @@ void ChartsWidget::updateState() {
|
||||
display_range.second = display_range.first + max_chart_range;
|
||||
} else if (cur_sec < (zoomed_range.first - 0.1) || cur_sec >= zoomed_range.second) {
|
||||
// loop in zoomed range
|
||||
can->seekTo(zoomed_range.first);
|
||||
QTimer::singleShot(0, [ts = zoomed_range.first]() { can->seekTo(ts);});
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &range = is_zoomed ? zoomed_range : display_range;
|
||||
|
||||
@@ -262,6 +262,9 @@ void MainWindow::openStream() {
|
||||
}
|
||||
stream->start();
|
||||
statusBar()->showMessage(tr("Route %1 loaded").arg(can->routeName()), 2000);
|
||||
} else if (!can) {
|
||||
stream = new DummyStream(this);
|
||||
stream->start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -138,13 +138,16 @@ void AbstractStream::updateLastMsgsTo(double sec) {
|
||||
auto prev = std::prev(it);
|
||||
double ts = (*prev)->mono_time / 1e9 - routeStartTime();
|
||||
auto &m = msgs[id];
|
||||
double freq = 0;
|
||||
// Keep suppressed bits.
|
||||
if (auto old_m = messages_.find(id); old_m != messages_.end()) {
|
||||
freq = old_m->second.freq;
|
||||
m.last_changes.reserve(old_m->second.last_changes.size());
|
||||
std::transform(old_m->second.last_changes.cbegin(), old_m->second.last_changes.cend(),
|
||||
std::back_inserter(m.last_changes),
|
||||
[](const auto &change) { return CanData::ByteLastChange{.suppressed = change.suppressed}; });
|
||||
}
|
||||
m.compute(id, (*prev)->dat, (*prev)->size, ts, getSpeed(), {});
|
||||
m.compute(id, (*prev)->dat, (*prev)->size, ts, getSpeed(), {}, freq);
|
||||
m.count = std::distance(ev.begin(), prev) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ public:
|
||||
signals:
|
||||
void paused();
|
||||
void resume();
|
||||
void seekingTo(double sec);
|
||||
void seekedTo(double sec);
|
||||
void streamStarted();
|
||||
void eventsMerged(const MessageEventsMap &events_map);
|
||||
@@ -107,6 +108,7 @@ protected:
|
||||
uint64_t lastEventMonoTime() const { return lastest_event_ts; }
|
||||
|
||||
std::vector<const CanEvent *> all_events_;
|
||||
double current_sec_ = 0;
|
||||
uint64_t lastest_event_ts = 0;
|
||||
|
||||
private:
|
||||
@@ -114,7 +116,6 @@ private:
|
||||
void updateLastMsgsTo(double sec);
|
||||
void updateMasks();
|
||||
|
||||
double current_sec_ = 0;
|
||||
MessageEventsMap events_;
|
||||
std::unordered_map<MessageId, CanData> last_msgs;
|
||||
std::unique_ptr<MonotonicBuffer> event_buffer_;
|
||||
|
||||
@@ -6,39 +6,12 @@
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QThread>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
// TODO: remove clearLayout
|
||||
static void clearLayout(QLayout* layout) {
|
||||
while (layout->count() > 0) {
|
||||
QLayoutItem* item = layout->takeAt(0);
|
||||
if (QWidget* widget = item->widget()) {
|
||||
widget->deleteLater();
|
||||
}
|
||||
if (QLayout* childLayout = item->layout()) {
|
||||
clearLayout(childLayout);
|
||||
}
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
PandaStream::PandaStream(QObject *parent, PandaStreamConfig config_) : config(config_), LiveStream(parent) {
|
||||
if (config.serial.isEmpty()) {
|
||||
auto serials = Panda::list();
|
||||
if (serials.size() == 0) {
|
||||
throw std::runtime_error("No panda found");
|
||||
}
|
||||
config.serial = QString::fromStdString(serials[0]);
|
||||
}
|
||||
|
||||
qDebug() << "Connecting to panda with serial" << config.serial;
|
||||
if (!connect()) {
|
||||
throw std::runtime_error("Failed to connect to panda");
|
||||
}
|
||||
}
|
||||
PandaStream::PandaStream(QObject *parent, PandaStreamConfig config_) : config(config_), LiveStream(parent) {}
|
||||
|
||||
bool PandaStream::connect() {
|
||||
try {
|
||||
qDebug() << "Connecting to panda with serial" << config.serial;
|
||||
panda.reset(new Panda(config.serial.toStdString()));
|
||||
config.bus_config.resize(3);
|
||||
qDebug() << "Connected";
|
||||
@@ -47,7 +20,6 @@ bool PandaStream::connect() {
|
||||
}
|
||||
|
||||
panda->set_safety_model(cereal::CarParams::SafetyModel::SILENT);
|
||||
|
||||
for (int bus = 0; bus < config.bus_config.size(); bus++) {
|
||||
panda->set_can_speed_kbps(bus, config.bus_config[bus].can_speed_kbps);
|
||||
|
||||
@@ -60,7 +32,6 @@ bool PandaStream::connect() {
|
||||
panda->set_data_speed_kbps(bus, 10);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -108,26 +79,14 @@ AbstractOpenStreamWidget *PandaStream::widget(AbstractStream **stream) {
|
||||
// OpenPandaWidget
|
||||
|
||||
OpenPandaWidget::OpenPandaWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->addStretch(1);
|
||||
|
||||
QFormLayout *form_layout = new QFormLayout();
|
||||
|
||||
form_layout = new QFormLayout(this);
|
||||
QHBoxLayout *serial_layout = new QHBoxLayout();
|
||||
serial_edit = new QComboBox();
|
||||
serial_edit->setFixedWidth(300);
|
||||
serial_layout->addWidget(serial_edit);
|
||||
serial_layout->addWidget(serial_edit = new QComboBox());
|
||||
|
||||
QPushButton *refresh = new QPushButton(tr("Refresh"));
|
||||
refresh->setFixedWidth(100);
|
||||
refresh->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
serial_layout->addWidget(refresh);
|
||||
form_layout->addRow(tr("Serial"), serial_layout);
|
||||
main_layout->addLayout(form_layout);
|
||||
|
||||
config_layout = new QFormLayout();
|
||||
main_layout->addLayout(config_layout);
|
||||
|
||||
main_layout->addStretch(1);
|
||||
|
||||
QObject::connect(refresh, &QPushButton::clicked, this, &OpenPandaWidget::refreshSerials);
|
||||
QObject::connect(serial_edit, &QComboBox::currentTextChanged, this, &OpenPandaWidget::buildConfigForm);
|
||||
@@ -145,15 +104,16 @@ void OpenPandaWidget::refreshSerials() {
|
||||
}
|
||||
|
||||
void OpenPandaWidget::buildConfigForm() {
|
||||
clearLayout(config_layout);
|
||||
QString serial = serial_edit->currentText();
|
||||
for (int i = form_layout->rowCount() - 1; i > 0; --i) {
|
||||
form_layout->removeRow(i);
|
||||
}
|
||||
|
||||
QString serial = serial_edit->currentText();
|
||||
bool has_fd = false;
|
||||
bool has_panda = !serial.isEmpty();
|
||||
|
||||
if (has_panda) {
|
||||
try {
|
||||
Panda panda = Panda(serial.toStdString());
|
||||
Panda panda(serial.toStdString());
|
||||
has_fd = (panda.hw_type == cereal::PandaState::PandaType::RED_PANDA) || (panda.hw_type == cereal::PandaState::PandaType::RED_PANDA_V2);
|
||||
} catch (const std::exception& e) {
|
||||
has_panda = false;
|
||||
@@ -201,20 +161,22 @@ void OpenPandaWidget::buildConfigForm() {
|
||||
QObject::connect(enable_fd, &QCheckBox::stateChanged, [=](int state) {config.bus_config[i].can_fd = (bool)state;});
|
||||
}
|
||||
|
||||
config_layout->addRow(tr("Bus %1:").arg(i), bus_layout);
|
||||
form_layout->addRow(tr("Bus %1:").arg(i), bus_layout);
|
||||
}
|
||||
} else {
|
||||
config.serial = "";
|
||||
config_layout->addWidget(new QLabel(tr("No panda found")));
|
||||
form_layout->addWidget(new QLabel(tr("No panda found")));
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenPandaWidget::open() {
|
||||
try {
|
||||
*stream = new PandaStream(qApp, config);
|
||||
} catch (std::exception &e) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to panda: '%1'").arg(e.what()));
|
||||
return false;
|
||||
if (!config.serial.isEmpty()) {
|
||||
auto panda_stream = std::make_unique<PandaStream>(qApp, config);
|
||||
if (panda_stream->connect()) {
|
||||
*stream = panda_stream.release();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to panda"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ class PandaStream : public LiveStream {
|
||||
Q_OBJECT
|
||||
public:
|
||||
PandaStream(QObject *parent, PandaStreamConfig config_ = {});
|
||||
bool connect();
|
||||
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
|
||||
inline QString routeName() const override {
|
||||
return QString("Live Streaming From Panda %1").arg(config.serial);
|
||||
@@ -28,7 +29,6 @@ public:
|
||||
|
||||
protected:
|
||||
void streamThread() override;
|
||||
bool connect();
|
||||
|
||||
std::unique_ptr<Panda> panda;
|
||||
PandaStreamConfig config = {};
|
||||
@@ -47,6 +47,6 @@ private:
|
||||
void buildConfigForm();
|
||||
|
||||
QComboBox *serial_edit;
|
||||
QFormLayout *config_layout;
|
||||
QFormLayout *form_layout;
|
||||
PandaStreamConfig config = {};
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <QPushButton>
|
||||
|
||||
#include "common/timing.h"
|
||||
#include "tools/cabana/streams/routes.h"
|
||||
|
||||
ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) {
|
||||
unsetenv("ZMQ");
|
||||
@@ -84,6 +85,16 @@ bool ReplayStream::eventFilter(const Event *event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReplayStream::seekTo(double ts) {
|
||||
// Update timestamp and notify receivers of the time change.
|
||||
current_sec_ = ts;
|
||||
std::set<MessageId> new_msgs;
|
||||
msgsReceived(&new_msgs, false);
|
||||
|
||||
// Seek to the specified timestamp
|
||||
replay->seekTo(std::max(double(0), ts), false);
|
||||
}
|
||||
|
||||
void ReplayStream::pause(bool pause) {
|
||||
replay->pause(pause);
|
||||
emit(pause ? paused() : resume());
|
||||
@@ -97,29 +108,36 @@ AbstractOpenStreamWidget *ReplayStream::widget(AbstractStream **stream) {
|
||||
// OpenReplayWidget
|
||||
|
||||
OpenReplayWidget::OpenReplayWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
|
||||
// TODO: get route list from api.comma.ai
|
||||
QGridLayout *grid_layout = new QGridLayout(this);
|
||||
grid_layout->addWidget(new QLabel(tr("Route")), 0, 0);
|
||||
grid_layout->addWidget(route_edit = new QLineEdit(this), 0, 1);
|
||||
route_edit->setPlaceholderText(tr("Enter remote route name or click browse to select a local route"));
|
||||
auto file_btn = new QPushButton(tr("Browse..."), this);
|
||||
grid_layout->addWidget(file_btn, 0, 2);
|
||||
route_edit->setPlaceholderText(tr("Enter route name or browse for local/remote route"));
|
||||
auto browse_remote_btn = new QPushButton(tr("Remote route..."), this);
|
||||
grid_layout->addWidget(browse_remote_btn, 0, 2);
|
||||
auto browse_local_btn = new QPushButton(tr("Local route..."), this);
|
||||
grid_layout->addWidget(browse_local_btn, 0, 3);
|
||||
|
||||
grid_layout->addWidget(new QLabel(tr("Camera")), 1, 0);
|
||||
QHBoxLayout *camera_layout = new QHBoxLayout();
|
||||
for (auto c : {tr("Road camera"), tr("Driver camera"), tr("Wide road camera")})
|
||||
camera_layout->addWidget(cameras.emplace_back(new QCheckBox(c, this)));
|
||||
cameras[0]->setChecked(true);
|
||||
camera_layout->addStretch(1);
|
||||
grid_layout->addItem(camera_layout, 1, 1);
|
||||
|
||||
setMinimumWidth(550);
|
||||
QObject::connect(file_btn, &QPushButton::clicked, [=]() {
|
||||
QObject::connect(browse_local_btn, &QPushButton::clicked, [=]() {
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Local Route"), settings.last_route_dir);
|
||||
if (!dir.isEmpty()) {
|
||||
route_edit->setText(dir);
|
||||
settings.last_route_dir = QFileInfo(dir).absolutePath();
|
||||
}
|
||||
});
|
||||
QObject::connect(browse_remote_btn, &QPushButton::clicked, [this]() {
|
||||
RoutesDialog route_dlg(this);
|
||||
if (route_dlg.exec()) {
|
||||
route_edit->setText(route_dlg.route());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool OpenReplayWidget::open() {
|
||||
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
void start() override;
|
||||
bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE);
|
||||
bool eventFilter(const Event *event);
|
||||
void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); }
|
||||
void seekTo(double ts) override;
|
||||
bool liveStreaming() const override { return false; }
|
||||
inline QString routeName() const override { return replay->route()->name(); }
|
||||
inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); }
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "tools/cabana/streams/routes.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFormLayout>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QListWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
|
||||
#include "system/hardware/hw.h"
|
||||
|
||||
// The RouteListWidget class extends QListWidget to display a custom message when empty
|
||||
class RouteListWidget : public QListWidget {
|
||||
public:
|
||||
RouteListWidget(QWidget *parent = nullptr) : QListWidget(parent) {}
|
||||
void setEmptyText(const QString &text) {
|
||||
empty_text_ = text;
|
||||
viewport()->update();
|
||||
}
|
||||
void paintEvent(QPaintEvent *event) override {
|
||||
QListWidget::paintEvent(event);
|
||||
if (count() == 0) {
|
||||
QPainter painter(viewport());
|
||||
painter.drawText(viewport()->rect(), Qt::AlignCenter, empty_text_);
|
||||
}
|
||||
}
|
||||
QString empty_text_ = tr("No items");
|
||||
};
|
||||
|
||||
RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent) {
|
||||
setWindowTitle(tr("Remote routes"));
|
||||
|
||||
QFormLayout *layout = new QFormLayout(this);
|
||||
layout->addRow(tr("Device"), device_list_ = new QComboBox(this));
|
||||
layout->addRow(tr("Duration"), period_selector_ = new QComboBox(this));
|
||||
layout->addRow(route_list_ = new RouteListWidget(this));
|
||||
auto button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
layout->addRow(button_box);
|
||||
|
||||
device_list_->addItem(tr("Loading..."));
|
||||
// Populate period selector with predefined durations
|
||||
period_selector_->addItem(tr("Last week"), 7);
|
||||
period_selector_->addItem(tr("Last 2 weeks"), 14);
|
||||
period_selector_->addItem(tr("Last month"), 30);
|
||||
period_selector_->addItem(tr("Last 6 months"), 180);
|
||||
|
||||
// Connect signals and slots
|
||||
connect(device_list_, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RoutesDialog::fetchRoutes);
|
||||
connect(period_selector_, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RoutesDialog::fetchRoutes);
|
||||
connect(route_list_, &QListWidget::itemDoubleClicked, this, &QDialog::accept);
|
||||
QObject::connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
QObject::connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
// Send request to fetch devices
|
||||
HttpRequest *http = new HttpRequest(this, !Hardware::PC());
|
||||
QObject::connect(http, &HttpRequest::requestDone, this, &RoutesDialog::parseDeviceList);
|
||||
http->sendRequest(CommaApi::BASE_URL + "/v1/me/devices/");
|
||||
}
|
||||
|
||||
void RoutesDialog::parseDeviceList(const QString &json, bool success, QNetworkReply::NetworkError err) {
|
||||
if (success) {
|
||||
device_list_->clear();
|
||||
auto devices = QJsonDocument::fromJson(json.toUtf8()).array();
|
||||
for (const QJsonValue &device : devices) {
|
||||
QString dongle_id = device["dongle_id"].toString();
|
||||
device_list_->addItem(dongle_id, dongle_id);
|
||||
}
|
||||
} else {
|
||||
bool unauthorized = (err == QNetworkReply::ContentAccessDenied || err == QNetworkReply::AuthenticationRequiredError);
|
||||
QMessageBox::warning(this, tr("Error"), unauthorized ? tr("Unauthorized, Authenticate with tools/lib/auth.py") : tr("Network error"));
|
||||
reject();
|
||||
}
|
||||
sender()->deleteLater();
|
||||
}
|
||||
|
||||
void RoutesDialog::fetchRoutes() {
|
||||
if (device_list_->currentIndex() == -1 || device_list_->currentData().isNull())
|
||||
return;
|
||||
|
||||
route_list_->clear();
|
||||
route_list_->setEmptyText(tr("Loading..."));
|
||||
|
||||
HttpRequest *http = new HttpRequest(this, !Hardware::PC());
|
||||
QObject::connect(http, &HttpRequest::requestDone, this, &RoutesDialog::parseRouteList);
|
||||
|
||||
// Construct URL with selected device and date range
|
||||
auto dongle_id = device_list_->currentData().toString();
|
||||
QDateTime current = QDateTime::currentDateTime();
|
||||
QString url = QString("%1/v1/devices/%2/routes_segments?start=%3&end=%4")
|
||||
.arg(CommaApi::BASE_URL).arg(dongle_id)
|
||||
.arg(current.addDays(-(period_selector_->currentData().toInt())).toMSecsSinceEpoch())
|
||||
.arg(current.toMSecsSinceEpoch());
|
||||
http->sendRequest(url);
|
||||
}
|
||||
|
||||
void RoutesDialog::parseRouteList(const QString &json, bool success, QNetworkReply::NetworkError err) {
|
||||
if (success) {
|
||||
for (const QJsonValue &route : QJsonDocument::fromJson(json.toUtf8()).array()) {
|
||||
uint64_t start_time = route["start_time_utc_millis"].toDouble();
|
||||
uint64_t end_time = route["end_time_utc_millis"].toDouble();
|
||||
auto datetime = QDateTime::fromMSecsSinceEpoch(start_time);
|
||||
auto item = new QListWidgetItem(QString("%1 %2min").arg(datetime.toString()).arg((end_time - start_time) / (1000 * 60)));
|
||||
item->setData(Qt::UserRole, route["fullname"].toString());
|
||||
route_list_->addItem(item);
|
||||
}
|
||||
// Select first route if available
|
||||
if (route_list_->count() > 0) route_list_->setCurrentRow(0);
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("Error"), tr("Failed to fetch routes. Check your network connection."));
|
||||
reject();
|
||||
}
|
||||
route_list_->setEmptyText(tr("No items"));
|
||||
sender()->deleteLater();
|
||||
}
|
||||
|
||||
void RoutesDialog::accept() {
|
||||
if (auto current_item = route_list_->currentItem()) {
|
||||
route_ = current_item->data(Qt::UserRole).toString();
|
||||
}
|
||||
QDialog::accept();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
|
||||
#include "selfdrive/ui/qt/api.h"
|
||||
|
||||
class RouteListWidget;
|
||||
|
||||
class RoutesDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
RoutesDialog(QWidget *parent);
|
||||
QString route() const { return route_; }
|
||||
|
||||
protected:
|
||||
void accept() override;
|
||||
void parseDeviceList(const QString &json, bool success, QNetworkReply::NetworkError err);
|
||||
void parseRouteList(const QString &json, bool success, QNetworkReply::NetworkError err);
|
||||
void fetchRoutes();
|
||||
|
||||
QComboBox *device_list_;
|
||||
QComboBox *period_selector_;
|
||||
RouteListWidget *route_list_;
|
||||
QString route_;
|
||||
};
|
||||
@@ -13,12 +13,8 @@
|
||||
|
||||
StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDialog(parent) {
|
||||
setWindowTitle(tr("Open stream"));
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
|
||||
QWidget *w = new QWidget(this);
|
||||
QVBoxLayout *layout = new QVBoxLayout(w);
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
tab = new QTabWidget(this);
|
||||
tab->setTabBarAutoHide(true);
|
||||
layout->addWidget(tab);
|
||||
|
||||
QHBoxLayout *dbc_layout = new QHBoxLayout();
|
||||
@@ -35,9 +31,8 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial
|
||||
line->setFrameStyle(QFrame::HLine | QFrame::Sunken);
|
||||
layout->addWidget(line);
|
||||
|
||||
main_layout->addWidget(w);
|
||||
auto btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
|
||||
main_layout->addWidget(btn_box);
|
||||
layout->addWidget(btn_box);
|
||||
|
||||
addStreamWidget(ReplayStream::widget(stream));
|
||||
addStreamWidget(PandaStream::widget(stream));
|
||||
@@ -48,14 +43,11 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial
|
||||
|
||||
QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
QObject::connect(btn_box, &QDialogButtonBox::accepted, [=]() {
|
||||
btn_box->button(QDialogButtonBox::Open)->setEnabled(false);
|
||||
w->setEnabled(false);
|
||||
setEnabled(false);
|
||||
if (((AbstractOpenStreamWidget *)tab->currentWidget())->open()) {
|
||||
accept();
|
||||
} else {
|
||||
btn_box->button(QDialogButtonBox::Open)->setEnabled(true);
|
||||
w->setEnabled(true);
|
||||
}
|
||||
setEnabled(true);
|
||||
});
|
||||
QObject::connect(file_btn, &QPushButton::clicked, [this]() {
|
||||
QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), settings.last_dir, "DBC (*.dbc)");
|
||||
|
||||
@@ -4,67 +4,37 @@ import argparse
|
||||
from collections import defaultdict
|
||||
from openpilot.selfdrive.debug.format_fingerprints import format_brand_fw_versions
|
||||
|
||||
from openpilot.selfdrive.car.fw_versions import match_fw_to_car
|
||||
from openpilot.selfdrive.car.interfaces import get_interface_attr
|
||||
from openpilot.selfdrive.car.fingerprints import MIGRATION
|
||||
from openpilot.selfdrive.car.fw_versions import MODEL_TO_BRAND, match_fw_to_car
|
||||
from openpilot.tools.lib.logreader import LogReader, ReadMode
|
||||
|
||||
|
||||
ALL_FW_VERSIONS = get_interface_attr("FW_VERSIONS")
|
||||
ALL_CARS = get_interface_attr("CAR")
|
||||
|
||||
PLATFORM_TO_PYTHON_CAR_NAME = {brand: {car.value: car.name for car in ALL_CARS[brand]} for brand in ALL_CARS}
|
||||
BRAND_TO_PLATFORMS = {brand: [car.value for car in ALL_CARS[brand]] for brand in ALL_CARS}
|
||||
PLATFORM_TO_BRAND = dict(sum([[(platform, brand) for platform in BRAND_TO_PLATFORMS[brand]] for brand in BRAND_TO_PLATFORMS], []))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Auto fingerprint from a route")
|
||||
parser.add_argument("route", help="The route name to use")
|
||||
parser.add_argument("platform", help="The platform, or leave empty to auto-determine using fuzzy", default=None, nargs='?')
|
||||
parser.add_argument("platform", help="The platform, or leave empty to auto-determine using fuzzy", default=None, nargs="?")
|
||||
args = parser.parse_args()
|
||||
|
||||
lr = LogReader(args.route, ReadMode.QLOG)
|
||||
|
||||
carFw = None
|
||||
carVin = None
|
||||
carPlatform = None
|
||||
|
||||
platform: str | None = None
|
||||
|
||||
CP = lr.first("carParams")
|
||||
assert CP is not None, "No carParams in route"
|
||||
|
||||
if CP is None:
|
||||
raise Exception("No fw versions in the provided route...")
|
||||
carPlatform = MIGRATION.get(CP.carFingerprint, CP.carFingerprint)
|
||||
|
||||
carFw = CP.carFw
|
||||
carVin = CP.carVin
|
||||
carPlatform = CP.carFingerprint
|
||||
|
||||
if args.platform is None: # attempt to auto-determine platform with other fuzzy fingerprints
|
||||
_, possible_platforms = match_fw_to_car(carFw, carVin, log=False)
|
||||
|
||||
if len(possible_platforms) != 1:
|
||||
print(f"Unable to auto-determine platform, possible platforms: {possible_platforms}")
|
||||
|
||||
if carPlatform != "MOCK":
|
||||
print("Using platform from route")
|
||||
platform = carPlatform
|
||||
else:
|
||||
platform = None
|
||||
else:
|
||||
platform = list(possible_platforms)[0]
|
||||
else:
|
||||
if args.platform is not None:
|
||||
platform = args.platform
|
||||
elif carPlatform != "MOCK":
|
||||
platform = carPlatform
|
||||
else:
|
||||
_, matches = match_fw_to_car(CP.carFw, CP.carVin, log=False)
|
||||
assert len(matches) == 1, f"Unable to auto-determine platform, matches: {matches}"
|
||||
platform = list(matches)[0]
|
||||
|
||||
if platform is None:
|
||||
raise Exception("unable to determine platform, try manually specifying the fingerprint.")
|
||||
|
||||
print("Attempting to add fw version for: ", platform)
|
||||
print("Attempting to add fw version for:", platform)
|
||||
|
||||
fw_versions: dict[str, dict[tuple, list[bytes]]] = defaultdict(lambda: defaultdict(list))
|
||||
brand = PLATFORM_TO_BRAND[platform]
|
||||
brand = MODEL_TO_BRAND[platform]
|
||||
|
||||
for fw in carFw:
|
||||
for fw in CP.carFw:
|
||||
if fw.brand == brand and not fw.logging:
|
||||
addr = fw.address
|
||||
subAddr = None if fw.subAddress == 0 else fw.subAddress
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"from openpilot.selfdrive.car.subaru.values import CAR, SubaruFlags\n",
|
||||
"from openpilot.selfdrive.car.subaru.fingerprints import FW_VERSIONS\n",
|
||||
"\n",
|
||||
"TEST_PLATFORMS = CAR.without_flags(SubaruFlags.PREGLOBAL)\n",
|
||||
"TEST_PLATFORMS = set(CAR) - CAR.with_flags(SubaruFlags.PREGLOBAL)\n",
|
||||
"\n",
|
||||
"Ecu = car.CarParams.Ecu\n",
|
||||
"\n",
|
||||
|
||||
@@ -12,7 +12,7 @@ def create_test_models_suite(routes: list[CarTestRoute], ci=False) -> unittest.T
|
||||
test_suite = unittest.TestSuite()
|
||||
for test_route in routes:
|
||||
# create new test case and discover tests
|
||||
test_case_args = {"car_model": test_route.car_model, "test_route": test_route, "ci": ci}
|
||||
test_case_args = {"platform": test_route.car_model, "test_route": test_route, "test_route_on_bucket": ci}
|
||||
CarModelTestCase = type("CarModelTestCase", (TestCarModel,), test_case_args)
|
||||
test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(CarModelTestCase))
|
||||
return test_suite
|
||||
|
||||
@@ -1,304 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import json
|
||||
import base64
|
||||
import os
|
||||
import subprocess
|
||||
from multiprocessing import Pool
|
||||
from openpilot.tools.lib.route import Route
|
||||
from openpilot.tools.lib.logreader import LogReader
|
||||
|
||||
try:
|
||||
from mcap.writer import Writer, CompressionType
|
||||
except ImportError:
|
||||
print("mcap module not found. Attempting to install...")
|
||||
subprocess.run([sys.executable, "-m", "pip", "install", "mcap"])
|
||||
# Attempt to import again after installation
|
||||
try:
|
||||
from mcap.writer import Writer, CompressionType
|
||||
except ImportError:
|
||||
print("Failed to install mcap module. Exiting.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
FOXGLOVE_IMAGE_SCHEME_TITLE = "foxglove.CompressedImage"
|
||||
FOXGLOVE_GEOJSON_TITLE = "foxglove.GeoJSON"
|
||||
FOXGLOVE_IMAGE_ENCODING = "base64"
|
||||
OUT_MCAP_FILE_NAME = "json_log.mcap"
|
||||
RLOG_FOLDER = "rlogs"
|
||||
SCHEMAS_FOLDER = "schemas"
|
||||
SCHEMA_EXTENSION = ".json"
|
||||
|
||||
schemas: dict[str, int] = {}
|
||||
channels: dict[str, int] = {}
|
||||
writer: Writer
|
||||
|
||||
|
||||
def convertBytesToString(data):
|
||||
if isinstance(data, bytes):
|
||||
return data.decode('latin-1') # Assuming UTF-8 encoding, adjust if needed
|
||||
elif isinstance(data, list):
|
||||
return [convertBytesToString(item) for item in data]
|
||||
elif isinstance(data, dict):
|
||||
return {key: convertBytesToString(value) for key, value in data.items()}
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
# Load jsonscheme for every Event
|
||||
def loadSchema(schemaName):
|
||||
with open(os.path.join(SCHEMAS_FOLDER, schemaName + SCHEMA_EXTENSION), "r") as file:
|
||||
return json.loads(file.read())
|
||||
|
||||
|
||||
# Foxglove creates one graph of an array, and not one for each item of an array
|
||||
# This can be avoided by transforming array to separate objects
|
||||
def transformListsToJsonDict(json_data):
|
||||
def convert_array_to_dict(array):
|
||||
new_dict = {}
|
||||
for index, item in enumerate(array):
|
||||
if isinstance(item, dict):
|
||||
new_dict[index] = transformListsToJsonDict(item)
|
||||
else:
|
||||
new_dict[index] = item
|
||||
return new_dict
|
||||
|
||||
new_data = {}
|
||||
for key, value in json_data.items():
|
||||
if isinstance(value, list):
|
||||
new_data[key] = convert_array_to_dict(value)
|
||||
elif isinstance(value, dict):
|
||||
new_data[key] = transformListsToJsonDict(value)
|
||||
else:
|
||||
new_data[key] = value
|
||||
return new_data
|
||||
|
||||
|
||||
# Transform openpilot thumbnail to foxglove compressedImage
|
||||
def transformToFoxgloveSchema(jsonMsg):
|
||||
bytesImgData = jsonMsg.get("thumbnail").get("thumbnail").encode('latin1')
|
||||
base64ImgData = base64.b64encode(bytesImgData)
|
||||
base64_string = base64ImgData.decode('utf-8')
|
||||
foxMsg = {
|
||||
"timestamp": {"sec": "0", "nsec": jsonMsg.get("logMonoTime")},
|
||||
"frame_id": str(jsonMsg.get("thumbnail").get("frameId")),
|
||||
"data": base64_string,
|
||||
"format": "jpeg",
|
||||
}
|
||||
return foxMsg
|
||||
|
||||
|
||||
# TODO: Check if there is a tool to build GEOJson
|
||||
def transformMapCoordinates(jsonMsg):
|
||||
coordinates = []
|
||||
for jsonCoords in jsonMsg.get("navRoute").get("coordinates"):
|
||||
coordinates.append([jsonCoords.get("longitude"), jsonCoords.get("latitude")])
|
||||
|
||||
# Define the GeoJSON
|
||||
geojson_data = {
|
||||
"type": "FeatureCollection",
|
||||
"features": [{"type": "Feature", "geometry": {"type": "LineString", "coordinates": coordinates}, "logMonoTime": jsonMsg.get("logMonoTime")}],
|
||||
}
|
||||
|
||||
# Create the final JSON with the GeoJSON data encoded as a string
|
||||
geoJson = {"geojson": json.dumps(geojson_data)}
|
||||
|
||||
return geoJson
|
||||
|
||||
|
||||
def jsonToScheme(jsonData):
|
||||
zeroArray = False
|
||||
schema = {"type": "object", "properties": {}, "required": []}
|
||||
for key, value in jsonData.items():
|
||||
if isinstance(value, dict):
|
||||
tempScheme, zeroArray = jsonToScheme(value)
|
||||
if tempScheme == 0:
|
||||
return 0
|
||||
schema["properties"][key] = tempScheme
|
||||
schema["required"].append(key)
|
||||
elif isinstance(value, list):
|
||||
if all(isinstance(item, dict) for item in value) and len(value) > 0: # Handle zero value arrays
|
||||
# Handle array of objects
|
||||
tempScheme, zeroArray = jsonToScheme(value[0])
|
||||
schema["properties"][key] = {"type": "array", "items": tempScheme if value else {}}
|
||||
schema["required"].append(key)
|
||||
else:
|
||||
if len(value) == 0:
|
||||
zeroArray = True
|
||||
# Handle array of primitive types
|
||||
schema["properties"][key] = {"type": "array", "items": {"type": "string"}}
|
||||
schema["required"].append(key)
|
||||
else:
|
||||
typeName = type(value).__name__
|
||||
if typeName == "str":
|
||||
typeName = "string"
|
||||
elif typeName == "bool":
|
||||
typeName = "boolean"
|
||||
elif typeName == "float":
|
||||
typeName = "number"
|
||||
elif typeName == "int":
|
||||
typeName = "integer"
|
||||
schema["properties"][key] = {"type": typeName}
|
||||
schema["required"].append(key)
|
||||
|
||||
return schema, zeroArray
|
||||
|
||||
|
||||
def saveScheme(scheme, schemaFileName):
|
||||
schemaFileName = schemaFileName + SCHEMA_EXTENSION
|
||||
# Create the new schemas folder
|
||||
os.makedirs(SCHEMAS_FOLDER, exist_ok=True)
|
||||
with open(os.path.join(SCHEMAS_FOLDER, schemaFileName), 'w') as json_file:
|
||||
json.dump(convertBytesToString(scheme), json_file)
|
||||
|
||||
|
||||
def convertToFoxGloveFormat(jsonData, rlogTopic):
|
||||
jsonData["title"] = rlogTopic
|
||||
if rlogTopic == "thumbnail":
|
||||
jsonData = transformToFoxgloveSchema(jsonData)
|
||||
jsonData["title"] = FOXGLOVE_IMAGE_SCHEME_TITLE
|
||||
elif rlogTopic == "navRoute":
|
||||
jsonData = transformMapCoordinates(jsonData)
|
||||
jsonData["title"] = FOXGLOVE_GEOJSON_TITLE
|
||||
else:
|
||||
jsonData = transformListsToJsonDict(jsonData)
|
||||
return jsonData
|
||||
|
||||
|
||||
def generateSchemas():
|
||||
listOfDirs = os.listdir(RLOG_FOLDER)
|
||||
# Open every dir in rlogs
|
||||
for directory in listOfDirs:
|
||||
# List every file in every rlog dir
|
||||
dirPath = os.path.join(RLOG_FOLDER, directory)
|
||||
listOfFiles = os.listdir(dirPath)
|
||||
lastIteration = len(listOfFiles)
|
||||
for iteration, file in enumerate(listOfFiles):
|
||||
# Load json data from every file until found one without empty arrays
|
||||
filePath = os.path.join(dirPath, file)
|
||||
with open(filePath, 'r') as jsonFile:
|
||||
jsonData = json.load(jsonFile)
|
||||
scheme, zerroArray = jsonToScheme(jsonData)
|
||||
# If array of len 0 has been found, type of its data can not be parsed, skip to the next log
|
||||
# in search for a non empty array. If there is not an non empty array in logs, put a dummy string type
|
||||
if zerroArray and not iteration == lastIteration - 1:
|
||||
continue
|
||||
title = jsonData.get("title")
|
||||
scheme["title"] = title
|
||||
# Add contentEncoding type, hardcoded in foxglove format
|
||||
if title == FOXGLOVE_IMAGE_SCHEME_TITLE:
|
||||
scheme["properties"]["data"]["contentEncoding"] = FOXGLOVE_IMAGE_ENCODING
|
||||
saveScheme(scheme, directory)
|
||||
break
|
||||
|
||||
|
||||
def downloadLogs(logPaths):
|
||||
segment_counter = 0
|
||||
for logPath in logPaths:
|
||||
segment_counter += 1
|
||||
msg_counter = 1
|
||||
print(segment_counter)
|
||||
rlog = LogReader(logPath)
|
||||
for msg in rlog:
|
||||
jsonMsg = json.loads(json.dumps(convertBytesToString(msg.to_dict())))
|
||||
jsonMsg = convertToFoxGloveFormat(jsonMsg, msg.which())
|
||||
rlog_dir_path = os.path.join(RLOG_FOLDER, msg.which())
|
||||
if not os.path.exists(rlog_dir_path):
|
||||
os.makedirs(rlog_dir_path)
|
||||
file_path = os.path.join(rlog_dir_path, str(segment_counter) + "," + str(msg_counter))
|
||||
with open(file_path, 'w') as json_file:
|
||||
json.dump(jsonMsg, json_file)
|
||||
msg_counter += 1
|
||||
|
||||
|
||||
def getLogMonoTime(jsonMsg):
|
||||
if jsonMsg.get("title") == FOXGLOVE_IMAGE_SCHEME_TITLE:
|
||||
logMonoTime = jsonMsg.get("timestamp").get("nsec")
|
||||
elif jsonMsg.get("title") == FOXGLOVE_GEOJSON_TITLE:
|
||||
logMonoTime = json.loads(jsonMsg.get("geojson")).get("features")[0].get("logMonoTime")
|
||||
else:
|
||||
logMonoTime = jsonMsg.get("logMonoTime")
|
||||
return logMonoTime
|
||||
|
||||
|
||||
def processMsgs(args):
|
||||
msgFile, rlogTopicPath, rlogTopic = args
|
||||
msgFilePath = os.path.join(rlogTopicPath, msgFile)
|
||||
with open(msgFilePath, "r") as file:
|
||||
jsonMsg = json.load(file)
|
||||
logMonoTime = getLogMonoTime(jsonMsg)
|
||||
return {'channel_id': channels[rlogTopic], 'log_time': logMonoTime, 'data': json.dumps(jsonMsg).encode("utf-8"), 'publish_time': logMonoTime}
|
||||
|
||||
|
||||
# Get logs from a path, and convert them into mcap
|
||||
def createMcap(logPaths):
|
||||
print(f"Downloading logs [{len(logPaths)}]")
|
||||
downloadLogs(logPaths)
|
||||
print("Creating schemas")
|
||||
generateSchemas()
|
||||
print("Creating mcap file")
|
||||
|
||||
listOfRlogTopics = os.listdir(RLOG_FOLDER)
|
||||
print(f"Registering schemas and channels [{len(listOfRlogTopics)}]")
|
||||
for counter, rlogTopic in enumerate(listOfRlogTopics):
|
||||
print(counter)
|
||||
schema = loadSchema(rlogTopic)
|
||||
schema_id = writer.register_schema(name=schema.get("title"), encoding="jsonschema", data=json.dumps(schema).encode())
|
||||
schemas[rlogTopic] = schema_id
|
||||
channel_id = writer.register_channel(schema_id=schemas[rlogTopic], topic=rlogTopic, message_encoding="json")
|
||||
channels[rlogTopic] = channel_id
|
||||
rlogTopicPath = os.path.join(RLOG_FOLDER, rlogTopic)
|
||||
msgFiles = os.listdir(rlogTopicPath)
|
||||
pool = Pool()
|
||||
results = pool.map(processMsgs, [(msgFile, rlogTopicPath, rlogTopic) for msgFile in msgFiles])
|
||||
pool.close()
|
||||
pool.join()
|
||||
for result in results:
|
||||
writer.add_message(channel_id=result['channel_id'], log_time=result['log_time'], data=result['data'], publish_time=result['publish_time'])
|
||||
|
||||
|
||||
def is_program_installed(program_name):
|
||||
try:
|
||||
# Check if the program is installed using dpkg (for traditional Debian packages)
|
||||
subprocess.run(["dpkg", "-l", program_name], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
# Check if the program is installed using snap
|
||||
try:
|
||||
subprocess.run(["snap", "list", program_name], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Example usage:
|
||||
program_name = "foxglove-studio" # Change this to the program you want to check
|
||||
if is_program_installed(program_name):
|
||||
print(f"{program_name} detected.")
|
||||
else:
|
||||
print(f"{program_name} could not be detected.")
|
||||
installFoxglove = input("Would you like to install it? YES/NO? - ")
|
||||
if installFoxglove.lower() == "yes":
|
||||
try:
|
||||
subprocess.run(['./install_foxglove.sh'], check=True)
|
||||
print("Installation completed successfully.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Installation failed with return code {e.returncode}.")
|
||||
# Get a route
|
||||
if len(sys.argv) == 1:
|
||||
route_name = "a2a0ccea32023010|2023-07-27--13-01-19"
|
||||
print("No route was provided, using demo route")
|
||||
else:
|
||||
route_name = sys.argv[1]
|
||||
# Get logs for a route
|
||||
print("Getting route log paths")
|
||||
route = Route(route_name)
|
||||
logPaths = route.log_paths()
|
||||
# Start mcap writer
|
||||
with open(OUT_MCAP_FILE_NAME, "wb") as stream:
|
||||
writer = Writer(stream, compression=CompressionType.NONE)
|
||||
writer.start()
|
||||
createMcap(logPaths)
|
||||
writer.finish()
|
||||
print(f"File {OUT_MCAP_FILE_NAME} has been successfully created. Please import it into foxglove studio to continue.")
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo "Installing foxglvoe studio..."
|
||||
sudo snap install foxglove-studio
|
||||
+27
-19
@@ -1,12 +1,13 @@
|
||||
#include "tools/replay/camera.h"
|
||||
|
||||
#include <capnp/dynamic.h>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "third_party/linux/include/msm_media_info.h"
|
||||
#include "tools/replay/util.h"
|
||||
|
||||
const int BUFFER_COUNT = 40;
|
||||
|
||||
std::tuple<size_t, size_t, size_t> get_nv12_info(int width, int height) {
|
||||
int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, width);
|
||||
int nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height);
|
||||
@@ -36,10 +37,12 @@ CameraServer::~CameraServer() {
|
||||
void CameraServer::startVipcServer() {
|
||||
vipc_server_.reset(new VisionIpcServer("camerad"));
|
||||
for (auto &cam : cameras_) {
|
||||
cam.cached_buf.clear();
|
||||
|
||||
if (cam.width > 0 && cam.height > 0) {
|
||||
rInfo("camera[%d] frame size %dx%d", cam.type, cam.width, cam.height);
|
||||
auto [nv12_width, nv12_height, nv12_buffer_size] = get_nv12_info(cam.width, cam.height);
|
||||
vipc_server_->create_buffers_with_sizes(cam.stream_type, YUV_BUFFER_COUNT, false, cam.width, cam.height,
|
||||
vipc_server_->create_buffers_with_sizes(cam.stream_type, BUFFER_COUNT, false, cam.width, cam.height,
|
||||
nv12_buffer_size, nv12_width, nv12_width * nv12_height);
|
||||
if (!cam.thread.joinable()) {
|
||||
cam.thread = std::thread(&CameraServer::cameraThread, this, std::ref(cam));
|
||||
@@ -50,13 +53,6 @@ void CameraServer::startVipcServer() {
|
||||
}
|
||||
|
||||
void CameraServer::cameraThread(Camera &cam) {
|
||||
auto read_frame = [&](FrameReader *fr, int frame_id) {
|
||||
VisionBuf *yuv_buf = vipc_server_->get_buffer(cam.stream_type);
|
||||
assert(yuv_buf);
|
||||
bool ret = fr->get(frame_id, yuv_buf);
|
||||
return ret ? yuv_buf : nullptr;
|
||||
};
|
||||
|
||||
while (true) {
|
||||
const auto [fr, event] = cam.queue.pop();
|
||||
if (!fr) break;
|
||||
@@ -66,29 +62,41 @@ void CameraServer::cameraThread(Camera &cam) {
|
||||
auto eidx = capnp::AnyStruct::Reader(evt).getPointerSection()[0].getAs<cereal::EncodeIndex>();
|
||||
if (eidx.getType() != cereal::EncodeIndex::Type::FULL_H_E_V_C) continue;
|
||||
|
||||
const int id = eidx.getSegmentId();
|
||||
bool prefetched = (id == cam.cached_id && eidx.getSegmentNum() == cam.cached_seg);
|
||||
auto yuv = prefetched ? cam.cached_buf : read_frame(fr, id);
|
||||
if (yuv) {
|
||||
int segment_id = eidx.getSegmentId();
|
||||
uint32_t frame_id = eidx.getFrameId();
|
||||
if (auto yuv = getFrame(cam, fr, segment_id, frame_id)) {
|
||||
VisionIpcBufExtra extra = {
|
||||
.frame_id = eidx.getFrameId(),
|
||||
.frame_id = frame_id,
|
||||
.timestamp_sof = eidx.getTimestampSof(),
|
||||
.timestamp_eof = eidx.getTimestampEof(),
|
||||
};
|
||||
yuv->set_frame_id(eidx.getFrameId());
|
||||
vipc_server_->send(yuv, &extra);
|
||||
} else {
|
||||
rError("camera[%d] failed to get frame: %lu", cam.type, eidx.getSegmentId());
|
||||
rError("camera[%d] failed to get frame: %lu", cam.type, segment_id);
|
||||
}
|
||||
|
||||
cam.cached_id = id + 1;
|
||||
cam.cached_seg = eidx.getSegmentNum();
|
||||
cam.cached_buf = read_frame(fr, cam.cached_id);
|
||||
// Prefetch the next frame
|
||||
getFrame(cam, fr, segment_id + 1, frame_id + 1);
|
||||
|
||||
--publishing_;
|
||||
}
|
||||
}
|
||||
|
||||
VisionBuf *CameraServer::getFrame(Camera &cam, FrameReader *fr, int32_t segment_id, uint32_t frame_id) {
|
||||
// Check if the frame is cached
|
||||
auto buf_it = std::find_if(cam.cached_buf.begin(), cam.cached_buf.end(),
|
||||
[frame_id](VisionBuf *buf) { return buf->get_frame_id() == frame_id; });
|
||||
if (buf_it != cam.cached_buf.end()) return *buf_it;
|
||||
|
||||
VisionBuf *yuv_buf = vipc_server_->get_buffer(cam.stream_type);
|
||||
if (fr->get(segment_id, yuv_buf)) {
|
||||
yuv_buf->set_frame_id(frame_id);
|
||||
cam.cached_buf.insert(yuv_buf);
|
||||
return yuv_buf;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CameraServer::pushFrame(CameraType type, FrameReader *fr, const Event *event) {
|
||||
auto &cam = cameras_[type];
|
||||
if (cam.width != fr->width || cam.height != fr->height) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
@@ -26,12 +27,11 @@ protected:
|
||||
int height;
|
||||
std::thread thread;
|
||||
SafeQueue<std::pair<FrameReader*, const Event *>> queue;
|
||||
int cached_id = -1;
|
||||
int cached_seg = -1;
|
||||
VisionBuf * cached_buf;
|
||||
std::set<VisionBuf *> cached_buf;
|
||||
};
|
||||
void startVipcServer();
|
||||
void cameraThread(Camera &cam);
|
||||
VisionBuf *getFrame(Camera &cam, FrameReader *fr, int32_t segment_id, uint32_t frame_id);
|
||||
|
||||
Camera cameras_[MAX_CAMERAS] = {
|
||||
{.type = RoadCam, .stream_type = VISION_STREAM_ROAD},
|
||||
|
||||
@@ -468,12 +468,15 @@ std::vector<Event>::const_iterator Replay::publishEvents(std::vector<Event>::con
|
||||
// Skip events if socket is not present
|
||||
if (!sockets_[evt.which]) continue;
|
||||
|
||||
int64_t time_diff = (evt.mono_time - evt_start_ts) / speed_ - (nanos_since_boot() - loop_start_ts);
|
||||
// if time_diff is greater than 1 second, it means that an invalid segment is skipped
|
||||
if (time_diff >= 1e9 || speed_ != prev_replay_speed) {
|
||||
// reset event start times
|
||||
const uint64_t current_nanos = nanos_since_boot();
|
||||
const int64_t time_diff = (evt.mono_time - evt_start_ts) / speed_ - (current_nanos - loop_start_ts);
|
||||
|
||||
// Reset timestamps for potential synchronization issues:
|
||||
// - A negative time_diff may indicate slow execution or system wake-up,
|
||||
// - A time_diff exceeding 1 second suggests a skipped segment.
|
||||
if ((time_diff < -1e9 || time_diff >= 1e9) || speed_ != prev_replay_speed) {
|
||||
evt_start_ts = evt.mono_time;
|
||||
loop_start_ts = nanos_since_boot();
|
||||
loop_start_ts = current_nanos;
|
||||
prev_replay_speed = speed_;
|
||||
} else if (time_diff > 0) {
|
||||
precise_nano_sleep(time_diff);
|
||||
|
||||
@@ -46,6 +46,7 @@ class SimulatorBridge(ABC):
|
||||
self.world: World | None = None
|
||||
|
||||
self.past_startup_engaged = False
|
||||
self.startup_button_prev = True
|
||||
|
||||
def _on_shutdown(self, signal, frame):
|
||||
self.shutdown()
|
||||
@@ -161,7 +162,8 @@ Ignition: {self.simulator_state.ignition} Engaged: {self.simulator_state.is_enga
|
||||
|
||||
self.past_startup_engaged = True
|
||||
elif not self.past_startup_engaged and controlsState.engageable:
|
||||
self.simulator_state.cruise_button = CruiseButtons.DECEL_SET # force engagement on startup
|
||||
self.simulator_state.cruise_button = CruiseButtons.DECEL_SET if self.startup_button_prev else CruiseButtons.MAIN # force engagement on startup
|
||||
self.startup_button_prev = not self.startup_button_prev
|
||||
|
||||
throttle_out = throttle_op if self.simulator_state.is_engaged else throttle_manual
|
||||
brake_out = brake_op if self.simulator_state.is_engaged else brake_manual
|
||||
|
||||
@@ -62,8 +62,6 @@ class TestSimBridgeBase(unittest.TestCase):
|
||||
while time.monotonic() < start_time + max_time_per_step:
|
||||
sm.update()
|
||||
|
||||
q.put("cruise_down") # Try engaging
|
||||
|
||||
if sm.all_alive() and sm['controlsState'].active:
|
||||
control_active += 1
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import ft4222
|
||||
import ft4222.I2CMaster
|
||||
|
||||
DEBUG = False
|
||||
|
||||
INA231_ADDR = 0x40
|
||||
INA231_REG_CONFIG = 0x00
|
||||
INA231_REG_SHUNT_VOLTAGE = 0x01
|
||||
INA231_REG_BUS_VOLTAGE = 0x02
|
||||
INA231_REG_POWER = 0x03
|
||||
INA231_REG_CURRENT = 0x04
|
||||
INA231_REG_CALIBRATION = 0x05
|
||||
|
||||
INA231_BUS_LSB = 1.25e-3
|
||||
INA231_SHUNT_LSB = 2.5e-6
|
||||
SHUNT_RESISTOR = 30e-3
|
||||
CURRENT_LSB = 1e-5
|
||||
|
||||
class Zookeeper:
|
||||
def __init__(self):
|
||||
if ft4222.createDeviceInfoList() < 2:
|
||||
raise Exception("No connected zookeeper found!")
|
||||
self.dev_a = ft4222.openByDescription("FT4222 A")
|
||||
self.dev_b = ft4222.openByDescription("FT4222 B")
|
||||
|
||||
if DEBUG:
|
||||
for i in range(ft4222.createDeviceInfoList()):
|
||||
print(f"Device {i}: {ft4222.getDeviceInfoDetail(i, False)}")
|
||||
|
||||
# Setup GPIO
|
||||
self.dev_b.gpio_Init(gpio2=ft4222.Dir.OUTPUT, gpio3=ft4222.Dir.OUTPUT)
|
||||
self.dev_b.setSuspendOut(False)
|
||||
self.dev_b.setWakeUpInterrut(False)
|
||||
|
||||
# Setup I2C
|
||||
self.dev_a.i2cMaster_Init(kbps=400)
|
||||
self._initialize_ina()
|
||||
|
||||
# Helper functions
|
||||
def _read_ina_register(self, register, length):
|
||||
self.dev_a.i2cMaster_WriteEx(INA231_ADDR, data=register, flag=ft4222.I2CMaster.Flag.REPEATED_START)
|
||||
return self.dev_a.i2cMaster_Read(INA231_ADDR, bytesToRead=length)
|
||||
|
||||
def _write_ina_register(self, register, data):
|
||||
msg = register.to_bytes(1, byteorder="big") + data.to_bytes(2, byteorder="big")
|
||||
self.dev_a.i2cMaster_Write(INA231_ADDR, data=msg)
|
||||
|
||||
def _initialize_ina(self):
|
||||
# Config
|
||||
self._write_ina_register(INA231_REG_CONFIG, 0x4127)
|
||||
|
||||
# Calibration
|
||||
CAL_VALUE = int(0.00512 / (CURRENT_LSB * SHUNT_RESISTOR))
|
||||
if DEBUG:
|
||||
print(f"Calibration value: {hex(CAL_VALUE)}")
|
||||
self._write_ina_register(INA231_REG_CALIBRATION, CAL_VALUE)
|
||||
|
||||
def _set_gpio(self, number, enabled):
|
||||
self.dev_b.gpio_Write(portNum=number, value=enabled)
|
||||
|
||||
# Public API functions
|
||||
def set_device_power(self, enabled):
|
||||
self._set_gpio(2, enabled)
|
||||
|
||||
def set_device_ignition(self, enabled):
|
||||
self._set_gpio(3, enabled)
|
||||
|
||||
def read_current(self):
|
||||
# Returns in A
|
||||
return int.from_bytes(self._read_ina_register(INA231_REG_CURRENT, 2), byteorder="big") * CURRENT_LSB
|
||||
|
||||
def read_power(self):
|
||||
# Returns in W
|
||||
return int.from_bytes(self._read_ina_register(INA231_REG_POWER, 2), byteorder="big") * CURRENT_LSB * 25
|
||||
|
||||
def read_voltage(self):
|
||||
# Returns in V
|
||||
return int.from_bytes(self._read_ina_register(INA231_REG_BUS_VOLTAGE, 2), byteorder="big") * INA231_BUS_LSB
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import time
|
||||
from openpilot.tools.zookeeper import Zookeeper
|
||||
|
||||
# Usage: check_consumption.py <averaging_time_sec> <max_average_power_W>
|
||||
# Exit code: 0 -> passed
|
||||
# 1 -> failed
|
||||
|
||||
if __name__ == "__main__":
|
||||
z = Zookeeper()
|
||||
|
||||
averaging_time_s = int(sys.argv[1])
|
||||
max_average_power = float(sys.argv[2])
|
||||
|
||||
start_time = time.time()
|
||||
measurements = []
|
||||
while time.time() - start_time < averaging_time_s:
|
||||
measurements.append(z.read_power())
|
||||
time.sleep(0.1)
|
||||
|
||||
average_power = sum(measurements)/len(measurements)
|
||||
print(f"Average power: {round(average_power, 4)}W")
|
||||
|
||||
if average_power > max_average_power:
|
||||
exit(1)
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from openpilot.tools.zookeeper import Zookeeper
|
||||
|
||||
if __name__ == "__main__":
|
||||
z = Zookeeper()
|
||||
z.set_device_power(False)
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from socket import gethostbyname, gaierror
|
||||
from openpilot.tools.zookeeper import Zookeeper
|
||||
|
||||
def is_online(ip):
|
||||
try:
|
||||
addr = gethostbyname(ip)
|
||||
return (os.system(f"ping -c 1 {addr} > /dev/null") == 0)
|
||||
except gaierror:
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
z = Zookeeper()
|
||||
z.set_device_power(True)
|
||||
|
||||
|
||||
ip = str(sys.argv[1])
|
||||
timeout = int(sys.argv[2])
|
||||
start_time = time.time()
|
||||
while not is_online(ip):
|
||||
print(f"{ip} not online yet!")
|
||||
|
||||
if time.time() - start_time > timeout:
|
||||
print("Timed out!")
|
||||
raise TimeoutError()
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
from openpilot.tools.zookeeper import Zookeeper
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
z = Zookeeper()
|
||||
z.set_device_ignition(1 if int(sys.argv[1]) > 0 else 0)
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
|
||||
from openpilot.common.realtime import Ratekeeper
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.tools.zookeeper import Zookeeper
|
||||
|
||||
if __name__ == "__main__":
|
||||
z = Zookeeper()
|
||||
z.set_device_power(True)
|
||||
z.set_device_ignition(False)
|
||||
|
||||
duration = None
|
||||
if len(sys.argv) > 1:
|
||||
duration = int(sys.argv[1])
|
||||
|
||||
rate = 123
|
||||
rk = Ratekeeper(rate, print_delay_threshold=None)
|
||||
fltr = FirstOrderFilter(0, 5, 1. / rate, initialized=False)
|
||||
|
||||
measurements = []
|
||||
start_time = time.monotonic()
|
||||
|
||||
try:
|
||||
while duration is None or time.monotonic() - start_time < duration:
|
||||
fltr.update(z.read_power())
|
||||
if rk.frame % rate == 0:
|
||||
measurements.append(fltr.x)
|
||||
t = datetime.timedelta(seconds=time.monotonic() - start_time)
|
||||
avg = sum(measurements) / len(measurements)
|
||||
print(f"Now: {fltr.x:.2f} W, Avg: {avg:.2f} W over {t}")
|
||||
rk.keep_time()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
t = datetime.timedelta(seconds=time.monotonic() - start_time)
|
||||
avg = sum(measurements) / len(measurements)
|
||||
print(f"\nAverage power: {avg:.2f}W over {t}")
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import time
|
||||
from openpilot.tools.zookeeper import Zookeeper
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
z = Zookeeper()
|
||||
z.set_device_power(True)
|
||||
|
||||
i = 0
|
||||
ign = False
|
||||
while 1:
|
||||
voltage = round(z.read_voltage(), 2)
|
||||
current = round(z.read_current(), 3)
|
||||
power = round(z.read_power(), 2)
|
||||
z.set_device_ignition(ign)
|
||||
print(f"Voltage: {voltage}V, Current: {current}A, Power: {power}W, Ignition: {ign}")
|
||||
|
||||
if i > 200:
|
||||
ign = not ign
|
||||
i = 0
|
||||
|
||||
i += 1
|
||||
time.sleep(0.1)
|
||||
Reference in New Issue
Block a user