Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ Note that a purely empty file could cause an error.
| `comm_stream_port` | `bool` | Port on which the streaming system runs on | Main, Remote | `16901` |
| `data_dir` | `str` | Directory for the data recording system | Main, Remote | `"data"` |
| `save_data` | `bool` | `True`: save data; `False`: discard data | Remote | `False` |
| `use_ltm` | `bool` | `True`: use long-term memory; `False`: short-term memory only | Main | `False` |

For device-related implicit configurations, please see the [devices module](leads_vec/devices.py).

Expand Down
1 change: 1 addition & 0 deletions leads/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from leads.event import *
from leads.leads import *
from leads.logger import Level, L
from leads.ltm import *
from leads.plugin import *
from leads.registry import *
from leads.sft import SFT, mark_device, read_device_marker
1 change: 1 addition & 0 deletions leads/_ltm/core
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
14 changes: 13 additions & 1 deletion leads/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from time import time as _time
from typing import override as _override, Any as _Any

from numpy import radians as _radians, degrees as _degrees, cos as _cos
from numpy import radians as _radians, degrees as _degrees, cos as _cos, sqrt as _sqrt


class Serializable(object):
Expand Down Expand Up @@ -161,6 +161,18 @@ def meters2dlon(meters: float, lat: float) -> float:
return _degrees(meters / 6378137 / _cos(_radians(lat)))


def distance_between(lat_0: float, lon_0: float, lat: float, lon: float) -> float:
"""
Calculate the distance between two locations on the Earth.
:param lat_0: the latitude of the first location
:param lon_0: the longitude of the first location
:param lat: the latitude of the second location
:param lon: the longitude of the second location
:return:
"""
return _sqrt(dlon2meters(lon - lon_0, .5 * (lat_0 + lat)) ** 2 + dlat2meters(lat - lat_0) ** 2)


def format_duration(duration: float) -> str:
"""
Wrap the duration into a formatted string.
Expand Down
4 changes: 3 additions & 1 deletion leads/data_persistence/analyzer/inference.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from abc import ABCMeta as _ABCMeta, abstractmethod as _abstractmethod
from typing import Any as _Any, override as _override, Generator as _Generator, Literal as _Literal

from leads.data import distance_between
from leads.data_persistence.analyzer.utils import time_invalid, speed_invalid, acceleration_invalid, \
mileage_invalid, latitude_invalid, longitude_invalid, distance_between
mileage_invalid, latitude_invalid, longitude_invalid
from leads.data_persistence.core import CSVDataset, DEFAULT_HEADER, VISUAL_HEADER_ONLY


Expand Down Expand Up @@ -239,6 +240,7 @@ class VisualDataRealignmentByLatency(Inference):
Offset the delay introduced by camera recording and video encoding so that the sensor data and the picture of the
same frame match.
"""

def __init__(self, *channels: _Literal["front", "left", "right", "rear"]) -> None:
super().__init__((0, 1), VISUAL_HEADER_ONLY)
self._channels: tuple[_Literal["front", "left", "right", "rear"], ...] = channels if channels else (
Expand Down
15 changes: 0 additions & 15 deletions leads/data_persistence/analyzer/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from typing import Any as _Any

from leads.data import dlat2meters, dlon2meters
from .._computational import sqrt as _sqrt


def time_invalid(o: _Any) -> bool:
return not isinstance(o, int)
Expand Down Expand Up @@ -30,15 +27,3 @@ def longitude_invalid(o: _Any) -> bool:

def latency_invalid(o: _Any) -> bool:
return not isinstance(o, int | float)


def distance_between(lat_0: float, lon_0: float, lat: float, lon: float) -> float:
"""
Calculate the distance between two locations on the Earth.
:param lat_0: the latitude of the first location
:param lon_0: the longitude of the first location
:param lat: the latitude of the second location
:param lon: the longitude of the second location
:return:
"""
return _sqrt(dlon2meters(lon - lon_0, .5 * (lat_0 + lat)) ** 2 + dlat2meters(lat - lat_0) ** 2)
32 changes: 32 additions & 0 deletions leads/ltm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from json import loads as _loads, dumps as _dumps
from os.path import abspath as _abspath

from leads.types import SupportedConfigValue as _SupportedConfigValue

_ltm: dict[str, _SupportedConfigValue] = {}


def _load_ltm() -> None:
global _ltm
with open(f"{_abspath(__file__)[:-6]}_ltm/core", "r") as f:
ltm_content = f.read()
if not (ltm_content.startswith("{") and ltm_content.endswith("}")):
ltm_content = "{}"
_ltm = _loads(ltm_content)


def _sync_ltm() -> None:
with open(f"{_abspath(__file__)[:-6]}_ltm/core", "w") as f:
f.write(_dumps(_ltm))


def ltm_get(key: str) -> _SupportedConfigValue:
return _ltm[key]


def ltm_set(key: str, value: _SupportedConfigValue) -> None:
_ltm[key] = value
_sync_ltm()


_load_ltm()
1 change: 1 addition & 0 deletions leads_vec/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ def __init__(self, base: dict[str, _Any]) -> None:
self.comm_stream: bool = False
self.comm_stream_port: int = 16901
self.data_dir: str = "data"
self.use_ltm: bool = False
super().__init__(base)
17 changes: 14 additions & 3 deletions leads_vec/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
Controller, CENTER_REAR_WHEEL_SPEED_SENSOR, require_config, mark_device, ODOMETER, GPS_RECEIVER, \
ConcurrentOdometer, LEFT_INDICATOR, RIGHT_INDICATOR, VOLTAGE_SENSOR, DataContainer, has_device, \
FRONT_VIEW_CAMERA, LEFT_VIEW_CAMERA, RIGHT_VIEW_CAMERA, REAR_VIEW_CAMERA, VisualDataContainer, BRAKE_INDICATOR, \
SFT, read_device_marker, has_controller, POWER_CONTROLLER, WHEEL_SPEED_CONTROLLER, ACCELEROMETER, require_context
SFT, read_device_marker, has_controller, POWER_CONTROLLER, WHEEL_SPEED_CONTROLLER, ACCELEROMETER, require_context, \
ltm_get, ltm_set, distance_between
from leads_arduino import ArduinoMicro, WheelSpeedSensor, VoltageSensor, Accelerometer, Acceleration
from leads_comm_serial import SOBD
from leads_gpio import NMEAGPSReceiver, LEDGroup, LED, LEDGroupCommand, LEDCommand, Entire, Transition, Button, \
Expand Down Expand Up @@ -54,16 +55,19 @@ def initialize(self, *parent_tags: str) -> None:
@override
def read(self) -> DataContainer:
general = {
"mileage": self.device(ODOMETER).read(),
"gps_valid": (gps := self.device(GPS_RECEIVER).read())[0],
"gps_ground_speed": gps[1],
"latitude": gps[2],
"longitude": gps[3],
**self.device(POWER_CONTROLLER).read()
}
wsc = self.device(WHEEL_SPEED_CONTROLLER).read()
odometer = self.device(ODOMETER)
if GPS_ONLY:
wsc["speed"] = gps[1]
prev = require_context().data()
odometer.write(odometer.read() + distance_between(prev.latitude, prev.longitude, gps[2], gps[3]))
general["mileage"] = odometer.read()
visual = {}
if has_device(FRONT_VIEW_CAMERA):
cam = get_camera(FRONT_VIEW_CAMERA, Base64Camera)
Expand Down Expand Up @@ -138,10 +142,17 @@ class AverageOdometer(ConcurrentOdometer):
def initialize(self, *parent_tags: str) -> None:
mark_device(self, "WSC")
super().initialize(*parent_tags)
if config.use_ltm:
self.write(ltm_get("mileage"))

@override
def write(self, payload: float) -> None:
super().write(payload)
ltm_set("mileage", payload)

@override
def read(self) -> float:
return super().read() / 3
return super().read() if GPS_ONLY else super().read() / 3


@device(*(((LEFT_FRONT_WHEEL_SPEED_SENSOR, RIGHT_FRONT_WHEEL_SPEED_SENSOR, CENTER_REAR_WHEEL_SPEED_SENSOR),
Expand Down