Params refactor, simplified (#2300)
* always c++ * Create C++ params class * get works * tests hang now * passes tests * cleanup string formatting * handle interrupt in blocking read * fix memory leak * remove unused constructor * Use delete_db_value directly * Rename put -> write_db_value * filename cleanup * no semicolons in cython * Update common/SConscript Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> * add std::string version of delete_db_value * This is handled * cleanup encoding * Add clear method to clear all * add persistent params * fix android build * Should be called clear_all * only import params when needed * set params path on manager import * recusrively create directories * Fix function order * cleanup mkdirp Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> Co-authored-by: Comma Device <device@comma.ai>pull/2324/head
parent
a4ccfcffe8
commit
2e182e5c57
20 changed files with 417 additions and 577 deletions
@ -1,409 +1,4 @@ |
|||||||
#!/usr/bin/env python3 |
from common.params_pyx import Params, UnknownKeyName, put_nonblocking # pylint: disable=no-name-in-module, import-error |
||||||
"""ROS has a parameter server, we have files. |
assert Params |
||||||
|
assert UnknownKeyName |
||||||
The parameter store is a persistent key value store, implemented as a directory with a writer lock. |
assert put_nonblocking |
||||||
On Android, we store params under params_dir = /data/params. The writer lock is a file |
|
||||||
"<params_dir>/.lock" taken using flock(), and data is stored in a directory symlinked to by |
|
||||||
"<params_dir>/d". |
|
||||||
|
|
||||||
Each key, value pair is stored as a file with named <key> with contents <value>, located in |
|
||||||
<params_dir>/d/<key> |
|
||||||
|
|
||||||
Readers of a single key can just open("<params_dir>/d/<key>") and read the file contents. |
|
||||||
Readers who want a consistent snapshot of multiple keys should take the lock. |
|
||||||
|
|
||||||
Writers should take the lock before modifying anything. Writers should also leave the DB in a |
|
||||||
consistent state after a crash. The implementation below does this by copying all params to a temp |
|
||||||
directory <params_dir>/<tmp>, then atomically symlinking <params_dir>/<d> to <params_dir>/<tmp> |
|
||||||
before deleting the old <params_dir>/<d> directory. |
|
||||||
|
|
||||||
Writers that only modify a single key can simply take the lock, then swap the corresponding value |
|
||||||
file in place without messing with <params_dir>/d. |
|
||||||
""" |
|
||||||
import time |
|
||||||
import os |
|
||||||
import errno |
|
||||||
import shutil |
|
||||||
import fcntl |
|
||||||
import tempfile |
|
||||||
import threading |
|
||||||
from enum import Enum |
|
||||||
from common.basedir import PARAMS |
|
||||||
|
|
||||||
|
|
||||||
def mkdirs_exists_ok(path): |
|
||||||
try: |
|
||||||
os.makedirs(path) |
|
||||||
except OSError: |
|
||||||
if not os.path.isdir(path): |
|
||||||
raise |
|
||||||
|
|
||||||
|
|
||||||
class TxType(Enum): |
|
||||||
PERSISTENT = 1 |
|
||||||
CLEAR_ON_MANAGER_START = 2 |
|
||||||
CLEAR_ON_PANDA_DISCONNECT = 3 |
|
||||||
|
|
||||||
|
|
||||||
class UnknownKeyName(Exception): |
|
||||||
pass |
|
||||||
|
|
||||||
|
|
||||||
keys = { |
|
||||||
"AccessToken": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"AthenadPid": [TxType.PERSISTENT], |
|
||||||
"CalibrationParams": [TxType.PERSISTENT], |
|
||||||
"CarBatteryCapacity": [TxType.PERSISTENT], |
|
||||||
"CarParams": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
|
||||||
"CarParamsCache": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
|
||||||
"CarVin": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
|
||||||
"CommunityFeaturesToggle": [TxType.PERSISTENT], |
|
||||||
"CompletedTrainingVersion": [TxType.PERSISTENT], |
|
||||||
"DisablePowerDown": [TxType.PERSISTENT], |
|
||||||
"DisableUpdates": [TxType.PERSISTENT], |
|
||||||
"DoUninstall": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"DongleId": [TxType.PERSISTENT], |
|
||||||
"GitBranch": [TxType.PERSISTENT], |
|
||||||
"GitCommit": [TxType.PERSISTENT], |
|
||||||
"GitRemote": [TxType.PERSISTENT], |
|
||||||
"GithubSshKeys": [TxType.PERSISTENT], |
|
||||||
"HasAcceptedTerms": [TxType.PERSISTENT], |
|
||||||
"HasCompletedSetup": [TxType.PERSISTENT], |
|
||||||
"IsDriverViewEnabled": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"IsLdwEnabled": [TxType.PERSISTENT], |
|
||||||
"IsMetric": [TxType.PERSISTENT], |
|
||||||
"IsOffroad": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"IsRHD": [TxType.PERSISTENT], |
|
||||||
"IsTakingSnapshot": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"IsUpdateAvailable": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"IsUploadRawEnabled": [TxType.PERSISTENT], |
|
||||||
"LastAthenaPingTime": [TxType.PERSISTENT], |
|
||||||
"LastUpdateTime": [TxType.PERSISTENT], |
|
||||||
"LastUpdateException": [TxType.PERSISTENT], |
|
||||||
"LiveParameters": [TxType.PERSISTENT], |
|
||||||
"OpenpilotEnabledToggle": [TxType.PERSISTENT], |
|
||||||
"LaneChangeEnabled": [TxType.PERSISTENT], |
|
||||||
"PandaFirmware": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
|
||||||
"PandaFirmwareHex": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
|
||||||
"PandaDongleId": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
|
||||||
"Passive": [TxType.PERSISTENT], |
|
||||||
"RecordFront": [TxType.PERSISTENT], |
|
||||||
"ReleaseNotes": [TxType.PERSISTENT], |
|
||||||
"ShouldDoUpdate": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"SubscriberInfo": [TxType.PERSISTENT], |
|
||||||
"TermsVersion": [TxType.PERSISTENT], |
|
||||||
"TrainingVersion": [TxType.PERSISTENT], |
|
||||||
"UpdateAvailable": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"UpdateFailedCount": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"Version": [TxType.PERSISTENT], |
|
||||||
"Offroad_ChargeDisabled": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
|
||||||
"Offroad_ConnectivityNeeded": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"Offroad_ConnectivityNeededPrompt": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"Offroad_TemperatureTooHigh": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"Offroad_PandaFirmwareMismatch": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
|
||||||
"Offroad_InvalidTime": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"Offroad_IsTakingSnapshot": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"Offroad_NeosUpdate": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
"Offroad_UpdateFailed": [TxType.CLEAR_ON_MANAGER_START], |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
def fsync_dir(path): |
|
||||||
fd = os.open(path, os.O_RDONLY) |
|
||||||
try: |
|
||||||
os.fsync(fd) |
|
||||||
finally: |
|
||||||
os.close(fd) |
|
||||||
|
|
||||||
|
|
||||||
class FileLock(): |
|
||||||
def __init__(self, path, create, lock_ex): |
|
||||||
self._path = path |
|
||||||
self._create = create |
|
||||||
self._fd = None |
|
||||||
self._lock_ex = lock_ex |
|
||||||
|
|
||||||
def acquire(self): |
|
||||||
self._fd = os.open(self._path, os.O_CREAT if self._create else 0) |
|
||||||
fcntl.flock(self._fd, fcntl.LOCK_EX if self._lock_ex else fcntl.LOCK_SH) |
|
||||||
|
|
||||||
def release(self): |
|
||||||
if self._fd is not None: |
|
||||||
os.close(self._fd) |
|
||||||
self._fd = None |
|
||||||
|
|
||||||
|
|
||||||
class DBAccessor(): |
|
||||||
def __init__(self, path): |
|
||||||
self._path = path |
|
||||||
self._vals = None |
|
||||||
|
|
||||||
def keys(self): |
|
||||||
self._check_entered() |
|
||||||
return self._vals.keys() |
|
||||||
|
|
||||||
def get(self, key): |
|
||||||
self._check_entered() |
|
||||||
|
|
||||||
if self._vals is None: |
|
||||||
return None |
|
||||||
|
|
||||||
try: |
|
||||||
return self._vals[key] |
|
||||||
except KeyError: |
|
||||||
return None |
|
||||||
|
|
||||||
def _get_lock(self, create, lock_ex): |
|
||||||
lock = FileLock(os.path.join(self._path, ".lock"), create, lock_ex) |
|
||||||
lock.acquire() |
|
||||||
return lock |
|
||||||
|
|
||||||
def _read_values_locked(self): |
|
||||||
"""Callers should hold a lock while calling this method.""" |
|
||||||
vals = {} |
|
||||||
try: |
|
||||||
data_path = self._data_path() |
|
||||||
keys = os.listdir(data_path) |
|
||||||
for key in keys: |
|
||||||
with open(os.path.join(data_path, key), "rb") as f: |
|
||||||
vals[key] = f.read() |
|
||||||
except (OSError, IOError) as e: |
|
||||||
# Either the DB hasn't been created yet, or somebody wrote a bug and left the DB in an |
|
||||||
# inconsistent state. Either way, return empty. |
|
||||||
if e.errno == errno.ENOENT: |
|
||||||
return {} |
|
||||||
|
|
||||||
return vals |
|
||||||
|
|
||||||
def _data_path(self): |
|
||||||
return os.path.join(self._path, "d") |
|
||||||
|
|
||||||
def _check_entered(self): |
|
||||||
if self._vals is None: |
|
||||||
raise Exception("Must call __enter__ before using DB") |
|
||||||
|
|
||||||
|
|
||||||
class DBReader(DBAccessor): |
|
||||||
def __enter__(self): |
|
||||||
try: |
|
||||||
lock = self._get_lock(False, False) |
|
||||||
except OSError as e: |
|
||||||
# Do not create lock if it does not exist. |
|
||||||
if e.errno == errno.ENOENT: |
|
||||||
self._vals = {} |
|
||||||
return self |
|
||||||
|
|
||||||
try: |
|
||||||
# Read everything. |
|
||||||
self._vals = self._read_values_locked() |
|
||||||
return self |
|
||||||
finally: |
|
||||||
lock.release() |
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback): |
|
||||||
pass |
|
||||||
|
|
||||||
|
|
||||||
class DBWriter(DBAccessor): |
|
||||||
def __init__(self, path): |
|
||||||
super(DBWriter, self).__init__(path) |
|
||||||
self._lock = None |
|
||||||
self._prev_umask = None |
|
||||||
|
|
||||||
def put(self, key, value): |
|
||||||
self._vals[key] = value |
|
||||||
|
|
||||||
def delete(self, key): |
|
||||||
self._vals.pop(key, None) |
|
||||||
|
|
||||||
def __enter__(self): |
|
||||||
mkdirs_exists_ok(self._path) |
|
||||||
|
|
||||||
# Make sure we can write and that permissions are correct. |
|
||||||
self._prev_umask = os.umask(0) |
|
||||||
|
|
||||||
try: |
|
||||||
os.chmod(self._path, 0o777) |
|
||||||
self._lock = self._get_lock(True, True) |
|
||||||
self._vals = self._read_values_locked() |
|
||||||
except Exception: |
|
||||||
os.umask(self._prev_umask) |
|
||||||
self._prev_umask = None |
|
||||||
raise |
|
||||||
|
|
||||||
return self |
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback): |
|
||||||
self._check_entered() |
|
||||||
|
|
||||||
try: |
|
||||||
# data_path refers to the externally used path to the params. It is a symlink. |
|
||||||
# old_data_path is the path currently pointed to by data_path. |
|
||||||
# tempdir_path is a path where the new params will go, which the new data path will point to. |
|
||||||
# new_data_path is a temporary symlink that will atomically overwrite data_path. |
|
||||||
# |
|
||||||
# The current situation is: |
|
||||||
# data_path -> old_data_path |
|
||||||
# We're going to write params data to tempdir_path |
|
||||||
# tempdir_path -> params data |
|
||||||
# Then point new_data_path to tempdir_path |
|
||||||
# new_data_path -> tempdir_path |
|
||||||
# Then atomically overwrite data_path with new_data_path |
|
||||||
# data_path -> tempdir_path |
|
||||||
old_data_path = None |
|
||||||
new_data_path = None |
|
||||||
tempdir_path = tempfile.mkdtemp(prefix=".tmp", dir=self._path) |
|
||||||
|
|
||||||
try: |
|
||||||
# Write back all keys. |
|
||||||
os.chmod(tempdir_path, 0o777) |
|
||||||
for k, v in self._vals.items(): |
|
||||||
with open(os.path.join(tempdir_path, k), "wb") as f: |
|
||||||
f.write(v) |
|
||||||
f.flush() |
|
||||||
os.fsync(f.fileno()) |
|
||||||
fsync_dir(tempdir_path) |
|
||||||
|
|
||||||
data_path = self._data_path() |
|
||||||
try: |
|
||||||
old_data_path = os.path.join(self._path, os.readlink(data_path)) |
|
||||||
except (OSError, IOError): |
|
||||||
# NOTE(mgraczyk): If other DB implementations have bugs, this could cause |
|
||||||
# copies to be left behind, but we still want to overwrite. |
|
||||||
pass |
|
||||||
|
|
||||||
new_data_path = "{}.link".format(tempdir_path) |
|
||||||
os.symlink(os.path.basename(tempdir_path), new_data_path) |
|
||||||
os.rename(new_data_path, data_path) |
|
||||||
fsync_dir(self._path) |
|
||||||
finally: |
|
||||||
# If the rename worked, we can delete the old data. Otherwise delete the new one. |
|
||||||
success = new_data_path is not None and os.path.exists(data_path) and ( |
|
||||||
os.readlink(data_path) == os.path.basename(tempdir_path)) |
|
||||||
|
|
||||||
if success: |
|
||||||
if old_data_path is not None: |
|
||||||
shutil.rmtree(old_data_path) |
|
||||||
else: |
|
||||||
shutil.rmtree(tempdir_path) |
|
||||||
|
|
||||||
# Regardless of what happened above, there should be no link at new_data_path. |
|
||||||
if new_data_path is not None and os.path.islink(new_data_path): |
|
||||||
os.remove(new_data_path) |
|
||||||
finally: |
|
||||||
os.umask(self._prev_umask) |
|
||||||
self._prev_umask = None |
|
||||||
|
|
||||||
# Always release the lock. |
|
||||||
self._lock.release() |
|
||||||
self._lock = None |
|
||||||
|
|
||||||
|
|
||||||
def read_db(params_path, key): |
|
||||||
path = "%s/d/%s" % (params_path, key) |
|
||||||
try: |
|
||||||
with open(path, "rb") as f: |
|
||||||
return f.read() |
|
||||||
except IOError: |
|
||||||
return None |
|
||||||
|
|
||||||
|
|
||||||
def write_db(params_path, key, value): |
|
||||||
if isinstance(value, str): |
|
||||||
value = value.encode('utf8') |
|
||||||
|
|
||||||
prev_umask = os.umask(0) |
|
||||||
lock = FileLock(params_path + "/.lock", True, True) |
|
||||||
lock.acquire() |
|
||||||
|
|
||||||
try: |
|
||||||
tmp_path = tempfile.NamedTemporaryFile(mode="wb", prefix=".tmp", dir=params_path, delete=False) |
|
||||||
with tmp_path as f: |
|
||||||
f.write(value) |
|
||||||
f.flush() |
|
||||||
os.fsync(f.fileno()) |
|
||||||
os.chmod(tmp_path.name, 0o666) |
|
||||||
|
|
||||||
path = "%s/d/%s" % (params_path, key) |
|
||||||
os.rename(tmp_path.name, path) |
|
||||||
fsync_dir(os.path.dirname(path)) |
|
||||||
finally: |
|
||||||
os.umask(prev_umask) |
|
||||||
lock.release() |
|
||||||
|
|
||||||
|
|
||||||
class Params(): |
|
||||||
def __init__(self, db=PARAMS): |
|
||||||
self.db = db |
|
||||||
|
|
||||||
# create the database if it doesn't exist... |
|
||||||
if not os.path.exists(self.db + "/d"): |
|
||||||
with self.transaction(write=True): |
|
||||||
pass |
|
||||||
|
|
||||||
def clear_all(self): |
|
||||||
shutil.rmtree(self.db, ignore_errors=True) |
|
||||||
with self.transaction(write=True): |
|
||||||
pass |
|
||||||
|
|
||||||
def transaction(self, write=False): |
|
||||||
if write: |
|
||||||
return DBWriter(self.db) |
|
||||||
else: |
|
||||||
return DBReader(self.db) |
|
||||||
|
|
||||||
def _clear_keys_with_type(self, tx_type): |
|
||||||
with self.transaction(write=True) as txn: |
|
||||||
for key in keys: |
|
||||||
if tx_type in keys[key]: |
|
||||||
txn.delete(key) |
|
||||||
|
|
||||||
def manager_start(self): |
|
||||||
self._clear_keys_with_type(TxType.CLEAR_ON_MANAGER_START) |
|
||||||
|
|
||||||
def panda_disconnect(self): |
|
||||||
self._clear_keys_with_type(TxType.CLEAR_ON_PANDA_DISCONNECT) |
|
||||||
|
|
||||||
def delete(self, key): |
|
||||||
with self.transaction(write=True) as txn: |
|
||||||
txn.delete(key) |
|
||||||
|
|
||||||
def get(self, key, block=False, encoding=None): |
|
||||||
if key not in keys: |
|
||||||
raise UnknownKeyName(key) |
|
||||||
|
|
||||||
while 1: |
|
||||||
ret = read_db(self.db, key) |
|
||||||
if not block or ret is not None: |
|
||||||
break |
|
||||||
# is polling really the best we can do? |
|
||||||
time.sleep(0.05) |
|
||||||
|
|
||||||
if ret is not None and encoding is not None: |
|
||||||
ret = ret.decode(encoding) |
|
||||||
|
|
||||||
return ret |
|
||||||
|
|
||||||
def put(self, key, dat): |
|
||||||
""" |
|
||||||
Warning: This function blocks until the param is written to disk! |
|
||||||
In very rare cases this can take over a second, and your code will hang. |
|
||||||
|
|
||||||
Use the put_nonblocking helper function in time sensitive code, but |
|
||||||
in general try to avoid writing params as much as possible. |
|
||||||
""" |
|
||||||
|
|
||||||
if key not in keys: |
|
||||||
raise UnknownKeyName(key) |
|
||||||
|
|
||||||
write_db(self.db, key, dat) |
|
||||||
|
|
||||||
|
|
||||||
def put_nonblocking(key, val): |
|
||||||
def f(key, val): |
|
||||||
params = Params() |
|
||||||
params.put(key, val) |
|
||||||
|
|
||||||
t = threading.Thread(target=f, args=(key, val)) |
|
||||||
t.start() |
|
||||||
return t |
|
||||||
|
@ -0,0 +1,16 @@ |
|||||||
|
from libcpp.string cimport string |
||||||
|
from libcpp cimport bool |
||||||
|
|
||||||
|
cdef extern from "selfdrive/common/params.cc": |
||||||
|
pass |
||||||
|
|
||||||
|
cdef extern from "selfdrive/common/util.c": |
||||||
|
pass |
||||||
|
|
||||||
|
cdef extern from "selfdrive/common/params.h": |
||||||
|
cdef cppclass Params: |
||||||
|
Params(bool) |
||||||
|
Params(string) |
||||||
|
string get(string, bool) nogil |
||||||
|
int delete_db_value(string) |
||||||
|
int write_db_value(string, string) |
@ -0,0 +1,157 @@ |
|||||||
|
# distutils: langauge = c++ |
||||||
|
import threading |
||||||
|
import os |
||||||
|
from libcpp cimport bool |
||||||
|
from libcpp.string cimport string |
||||||
|
from params_pxd cimport Params as c_Params |
||||||
|
|
||||||
|
cdef enum TxType: |
||||||
|
PERSISTENT = 1 |
||||||
|
CLEAR_ON_MANAGER_START = 2 |
||||||
|
CLEAR_ON_PANDA_DISCONNECT = 3 |
||||||
|
|
||||||
|
keys = { |
||||||
|
b"AccessToken": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"AthenadPid": [TxType.PERSISTENT], |
||||||
|
b"CalibrationParams": [TxType.PERSISTENT], |
||||||
|
b"CarBatteryCapacity": [TxType.PERSISTENT], |
||||||
|
b"CarParams": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||||
|
b"CarParamsCache": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||||
|
b"CarVin": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||||
|
b"CommunityFeaturesToggle": [TxType.PERSISTENT], |
||||||
|
b"CompletedTrainingVersion": [TxType.PERSISTENT], |
||||||
|
b"DisablePowerDown": [TxType.PERSISTENT], |
||||||
|
b"DisableUpdates": [TxType.PERSISTENT], |
||||||
|
b"DoUninstall": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"DongleId": [TxType.PERSISTENT], |
||||||
|
b"GitBranch": [TxType.PERSISTENT], |
||||||
|
b"GitCommit": [TxType.PERSISTENT], |
||||||
|
b"GitRemote": [TxType.PERSISTENT], |
||||||
|
b"GithubSshKeys": [TxType.PERSISTENT], |
||||||
|
b"HasAcceptedTerms": [TxType.PERSISTENT], |
||||||
|
b"HasCompletedSetup": [TxType.PERSISTENT], |
||||||
|
b"IsDriverViewEnabled": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"IsLdwEnabled": [TxType.PERSISTENT], |
||||||
|
b"IsMetric": [TxType.PERSISTENT], |
||||||
|
b"IsOffroad": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"IsRHD": [TxType.PERSISTENT], |
||||||
|
b"IsTakingSnapshot": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"IsUpdateAvailable": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"IsUploadRawEnabled": [TxType.PERSISTENT], |
||||||
|
b"LastAthenaPingTime": [TxType.PERSISTENT], |
||||||
|
b"LastUpdateTime": [TxType.PERSISTENT], |
||||||
|
b"LastUpdateException": [TxType.PERSISTENT], |
||||||
|
b"LiveParameters": [TxType.PERSISTENT], |
||||||
|
b"OpenpilotEnabledToggle": [TxType.PERSISTENT], |
||||||
|
b"LaneChangeEnabled": [TxType.PERSISTENT], |
||||||
|
b"PandaFirmware": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||||
|
b"PandaFirmwareHex": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||||
|
b"PandaDongleId": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||||
|
b"Passive": [TxType.PERSISTENT], |
||||||
|
b"RecordFront": [TxType.PERSISTENT], |
||||||
|
b"ReleaseNotes": [TxType.PERSISTENT], |
||||||
|
b"ShouldDoUpdate": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"SubscriberInfo": [TxType.PERSISTENT], |
||||||
|
b"TermsVersion": [TxType.PERSISTENT], |
||||||
|
b"TrainingVersion": [TxType.PERSISTENT], |
||||||
|
b"UpdateAvailable": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"UpdateFailedCount": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"Version": [TxType.PERSISTENT], |
||||||
|
b"Offroad_ChargeDisabled": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||||
|
b"Offroad_ConnectivityNeeded": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"Offroad_ConnectivityNeededPrompt": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"Offroad_TemperatureTooHigh": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"Offroad_PandaFirmwareMismatch": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||||
|
b"Offroad_InvalidTime": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"Offroad_IsTakingSnapshot": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"Offroad_NeosUpdate": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
b"Offroad_UpdateFailed": [TxType.CLEAR_ON_MANAGER_START], |
||||||
|
} |
||||||
|
|
||||||
|
def ensure_bytes(v): |
||||||
|
if isinstance(v, str): |
||||||
|
return v.encode() |
||||||
|
else: |
||||||
|
return v |
||||||
|
|
||||||
|
|
||||||
|
class UnknownKeyName(Exception): |
||||||
|
pass |
||||||
|
|
||||||
|
cdef class Params: |
||||||
|
cdef c_Params* p |
||||||
|
|
||||||
|
def __cinit__(self, d=None, persistent_params=False): |
||||||
|
if d is not None: |
||||||
|
self.p = new c_Params(<string>d.encode()) |
||||||
|
else: |
||||||
|
self.p = new c_Params(<bool>persistent_params) |
||||||
|
|
||||||
|
def __dealloc__(self): |
||||||
|
del self.p |
||||||
|
|
||||||
|
def clear_all(self, tx_type=None): |
||||||
|
for key in keys: |
||||||
|
if tx_type is None or tx_type in keys[key]: |
||||||
|
self.delete(key) |
||||||
|
|
||||||
|
def manager_start(self): |
||||||
|
self.clear_all(TxType.CLEAR_ON_MANAGER_START) |
||||||
|
|
||||||
|
def panda_disconnect(self): |
||||||
|
self.clear_all(TxType.CLEAR_ON_PANDA_DISCONNECT) |
||||||
|
|
||||||
|
def get(self, key, block=False, encoding=None): |
||||||
|
key = ensure_bytes(key) |
||||||
|
|
||||||
|
if key not in keys: |
||||||
|
raise UnknownKeyName(key) |
||||||
|
|
||||||
|
cdef string k = key |
||||||
|
cdef bool b = block |
||||||
|
|
||||||
|
cdef string val |
||||||
|
with nogil: |
||||||
|
val = self.p.get(k, b) |
||||||
|
|
||||||
|
if val == b"": |
||||||
|
if block: |
||||||
|
# If we got no value while running in blocked mode |
||||||
|
# it means we got an interrupt while waiting |
||||||
|
raise KeyboardInterrupt |
||||||
|
else: |
||||||
|
return None |
||||||
|
|
||||||
|
if encoding is not None: |
||||||
|
return val.decode(encoding) |
||||||
|
else: |
||||||
|
return val |
||||||
|
|
||||||
|
def put(self, key, dat): |
||||||
|
""" |
||||||
|
Warning: This function blocks until the param is written to disk! |
||||||
|
In very rare cases this can take over a second, and your code will hang. |
||||||
|
Use the put_nonblocking helper function in time sensitive code, but |
||||||
|
in general try to avoid writing params as much as possible. |
||||||
|
""" |
||||||
|
key = ensure_bytes(key) |
||||||
|
dat = ensure_bytes(dat) |
||||||
|
|
||||||
|
if key not in keys: |
||||||
|
raise UnknownKeyName(key) |
||||||
|
|
||||||
|
self.p.write_db_value(key, dat) |
||||||
|
|
||||||
|
def delete(self, key): |
||||||
|
key = ensure_bytes(key) |
||||||
|
self.p.delete_db_value(key) |
||||||
|
|
||||||
|
|
||||||
|
def put_nonblocking(key, val, d=None): |
||||||
|
def f(key, val): |
||||||
|
params = Params(d) |
||||||
|
params.put(key, val) |
||||||
|
|
||||||
|
t = threading.Thread(target=f, args=(key, val)) |
||||||
|
t.start() |
||||||
|
return t |
@ -0,0 +1,21 @@ |
|||||||
|
import os |
||||||
|
from distutils.core import Extension, setup |
||||||
|
from Cython.Build import cythonize |
||||||
|
from common.cython_hacks import BuildExtWithoutPlatformSuffix |
||||||
|
from common.basedir import BASEDIR |
||||||
|
|
||||||
|
sourcefiles = ['params_pyx.pyx'] |
||||||
|
extra_compile_args = ["-std=c++11"] |
||||||
|
|
||||||
|
setup(name='common', |
||||||
|
cmdclass={'build_ext': BuildExtWithoutPlatformSuffix}, |
||||||
|
ext_modules=cythonize( |
||||||
|
Extension( |
||||||
|
"params_pyx", |
||||||
|
language="c++", |
||||||
|
sources=sourcefiles, |
||||||
|
include_dirs=[BASEDIR, os.path.join(BASEDIR, 'selfdrive')], |
||||||
|
extra_compile_args=extra_compile_args |
||||||
|
) |
||||||
|
) |
||||||
|
) |
@ -1,44 +1,47 @@ |
|||||||
#pragma once |
#pragma once |
||||||
#include <stddef.h> |
#include <stddef.h> |
||||||
|
|
||||||
#ifdef __cplusplus |
|
||||||
extern "C" { |
|
||||||
#endif |
|
||||||
|
|
||||||
#define ERR_NO_VALUE -33 |
|
||||||
|
|
||||||
int write_db_value(const char* key, const char* value, size_t value_size, bool persistent_param = false); |
|
||||||
|
|
||||||
// Reads a value from the params database.
|
|
||||||
// Inputs:
|
|
||||||
// key: The key to read.
|
|
||||||
// value: A pointer where a newly allocated string containing the db value will
|
|
||||||
// be written.
|
|
||||||
// value_sz: A pointer where the size of value will be written. Does not
|
|
||||||
// include the NULL terminator.
|
|
||||||
// persistent_param: Boolean indicating if the param store in the /persist partition is to be used.
|
|
||||||
// e.g. for sensor calibration files. Will not be cleared after wipe or re-install.
|
|
||||||
//
|
|
||||||
// Returns: Negative on failure, otherwise 0.
|
|
||||||
int read_db_value(const char* key, char** value, size_t* value_sz, bool persistent_param = false); |
|
||||||
|
|
||||||
// Delete a value from the params database.
|
|
||||||
// Inputs are the same as read_db_value, without value and value_sz.
|
|
||||||
int delete_db_value(const char* key, bool persistent_param = false); |
|
||||||
|
|
||||||
// Reads a value from the params database, blocking until successful.
|
|
||||||
// Inputs are the same as read_db_value.
|
|
||||||
void read_db_value_blocking(const char* key, char** value, size_t* value_sz, bool persistent_param = false); |
|
||||||
|
|
||||||
#ifdef __cplusplus |
|
||||||
} // extern "C"
|
|
||||||
#endif |
|
||||||
|
|
||||||
#ifdef __cplusplus |
|
||||||
#include <map> |
#include <map> |
||||||
#include <string> |
#include <string> |
||||||
#include <vector> |
#include <vector> |
||||||
int read_db_all(std::map<std::string, std::string> *params, bool persistent_param = false); |
|
||||||
std::vector<char> read_db_bytes(const char* param_name, bool persistent_param = false); |
#define ERR_NO_VALUE -33 |
||||||
bool read_db_bool(const char* param_name, bool persistent_param = false); |
|
||||||
#endif |
class Params { |
||||||
|
private: |
||||||
|
std::string params_path; |
||||||
|
|
||||||
|
public: |
||||||
|
Params(bool persistent_param = false); |
||||||
|
Params(std::string path); |
||||||
|
|
||||||
|
int write_db_value(std::string key, std::string dat); |
||||||
|
int write_db_value(const char* key, const char* value, size_t value_size); |
||||||
|
|
||||||
|
// Reads a value from the params database.
|
||||||
|
// Inputs:
|
||||||
|
// key: The key to read.
|
||||||
|
// value: A pointer where a newly allocated string containing the db value will
|
||||||
|
// be written.
|
||||||
|
// value_sz: A pointer where the size of value will be written. Does not
|
||||||
|
// include the NULL terminator.
|
||||||
|
// persistent_param: Boolean indicating if the param store in the /persist partition is to be used.
|
||||||
|
// e.g. for sensor calibration files. Will not be cleared after wipe or re-install.
|
||||||
|
//
|
||||||
|
// Returns: Negative on failure, otherwise 0.
|
||||||
|
int read_db_value(const char* key, char** value, size_t* value_sz); |
||||||
|
|
||||||
|
// Delete a value from the params database.
|
||||||
|
// Inputs are the same as read_db_value, without value and value_sz.
|
||||||
|
int delete_db_value(std::string key); |
||||||
|
int delete_db_value(const char* key); |
||||||
|
|
||||||
|
// Reads a value from the params database, blocking until successful.
|
||||||
|
// Inputs are the same as read_db_value.
|
||||||
|
int read_db_value_blocking(const char* key, char** value, size_t* value_sz); |
||||||
|
|
||||||
|
int read_db_all(std::map<std::string, std::string> *params); |
||||||
|
std::vector<char> read_db_bytes(const char* param_name); |
||||||
|
bool read_db_bool(const char* param_name); |
||||||
|
|
||||||
|
std::string get(std::string key, bool block=false); |
||||||
|
}; |
||||||
|
Loading…
Reference in new issue