Skip to content

Commit

Permalink
Tweaks for getting tracksight to work on Mac (#1278)
Browse files Browse the repository at this point in the history
### Changelist 
<!-- Give a list of the changes covered in this PR. This will help both
you and the reviewer keep this PR within scope. -->

Get tracksight to work on MacOS. Also, delete a bunch of old files. Also
also, add some documentation to the Python code.

Duplicat of #1275 cuz I'm silly
  • Loading branch information
gtaharaedmonds authored May 23, 2024
1 parent 97c582e commit 8f44cf0
Show file tree
Hide file tree
Showing 37 changed files with 88 additions and 1,750 deletions.
25 changes: 2 additions & 23 deletions .github/workflows/tracksight-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,16 @@ on:
- "software/tracksight/backend/**"
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubcformulaelectric/environment:latest

steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Setup Miniconda
uses: conda-incubator/setup-miniconda@v2
with:
activate-environment: tracksight-backend
environment-file: software/tracksight/backend/environment.yml
python-version: 3
auto-activate-base: false

- name: Conda Environment Information
shell: bash -l {0}
run: |
conda info
conda list

- name: Start Flask app
run: |
cd software/tracksight/backend
source /usr/share/miniconda/bin/activate tracksight-backend
python app/telemetry.py &
sleep 10
python3 app/telemetry.py & sleep 10
curl --fail http://localhost:5000
env:
FLASK_ENV: development
Expand All @@ -39,10 +24,4 @@ jobs:
if: always()
run: kill $(jobs -p) || true

- name: CI Testing
run: |
cd software/tracksight/backend
source /usr/share/miniconda/bin/activate tracksight-backend
conda info
pytest app/tests

13 changes: 12 additions & 1 deletion environment/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Firmware dependencies.
cmake
schema
strenum
Expand All @@ -13,4 +14,14 @@ pyserial
protoletariat
pybind11
pytest
rich
rich

# Tracksight backend dependencies.
flask
Definitions
flask_cors
pandas
flask_socketio
pytest
asammdf
requests
1 change: 1 addition & 0 deletions software/tracksight/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
frontend/logs
frontend/*.log
frontend/npm-debug.log*
backend/**/*.log

# Runtime data
frontend/pids
Expand Down
1 change: 0 additions & 1 deletion software/tracksight/backend/.gitignore

This file was deleted.

19 changes: 4 additions & 15 deletions software/tracksight/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
# ENV FLASK_APP=app.py
# ENV FLASK_RUN_HOST=0.0.0.0
FROM python:3.11-slim
FROM python:3.11

# for healthcheck
RUN apt update && apt install -y curl && apt clean

USER root
WORKDIR /backend
RUN pip install pipenv
COPY environment/requirements.txt .
RUN pip3 install -r requirements.txt

WORKDIR /backend
COPY Pipfile .
COPY Pipfile.lock .
RUN pipenv install --system --deploy

WORKDIR /backend
COPY ./software/tracksight/backend/ .
COPY software/tracksight/backend/ .

USER root
WORKDIR /backend
EXPOSE 5000
CMD ["python", "app/telemetry.py"]
3 changes: 0 additions & 3 deletions software/tracksight/backend/app/process/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,4 @@
This module is responsible for the process of the application. It is the main module of the application.
"""

# from http_app import app as http_app


__all__ = ["signal_util", "influx_handler", "definitions"]

This file was deleted.

47 changes: 17 additions & 30 deletions software/tracksight/backend/app/process/flask_apps/http_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,66 @@
Main REST component of the backend
"""

from flask import Blueprint, jsonify, request
from typing import Tuple
from flask import Blueprint, jsonify, request, Response

from ..influx_handler import (
InfluxHandler as influx,
)
from ..influx_handler import (
NoDataForQueryException,
)
from ..influx_handler import InfluxHandler as influx
from ..influx_handler import NoDataForQueryException

# HTTP processes for data that is not live
app = Blueprint("http_app", __name__)

# helpers
from ..signal_util import SignalUtil

signal_util = SignalUtil()


def responsify(data):
"""
Manual CORS?????? very bad idea
:param data:
:return:
:param data: Data to JSON-ify.
:returns JSON-ified data response.
"""
response = jsonify(data)
response.headers.add("Access-Control-Allow-Origin", "*")
return response


@app.route("/")
def hello_world():
def hello_world() -> str:
"""
:return:
:returns Hello world page for backend.
"""
return "Telemetry Backend is running!"


@app.route("/health")
def health():
def health() -> Tuple[Response, int]:
"""
:return:
:returns Health check page for backend.
"""
return jsonify(status="healthy"), 200


@app.route("/signal/measurements", methods=["GET"])
def return_all_measurements():
def return_all_measurements() -> Response:
"""
:return:
:returns Page displaying all measurements in the database.
"""
measurements = influx.get_measurements()
return responsify(measurements)


@app.route("/signal/measurement/<string:measurement>/fields", methods=["GET"])
def return_all_fields_for_measurement(measurement):
def return_all_fields_for_measurement(measurement: str) -> Response:
"""
:param measurement:
:return:
:param measurement: Measurement to fetch fields for.
:returns Page displaying all fields for a specific measurement.
"""
fields = influx.get_fields(measurement)
return responsify(fields)


@app.route("/signal/query", methods=["GET"])
def return_query():
def return_query() -> Response:
"""
:return:
:returns Page displaying the result of a single query.
"""
params = request.args
measurement = params.get("measurement")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@

logger = logging.getLogger("telemetry_logger")

from ..signal_util import SignalUtil

signal_util = SignalUtil()

# SocketIO processes for live data
socketio = flask_socketio.SocketIO(cors_allowed_origins="*")

Expand Down
89 changes: 40 additions & 49 deletions software/tracksight/backend/app/process/influx_handler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""
Influx Handler
Influx database handler class.
"""

from typing import List, Tuple, Dict
import requests
from dateutil.parser import parse

Expand All @@ -12,52 +13,37 @@


class NoDataForQueryException(Exception):
"""Raised when no data was found for a specific query"""
"""
Raised when no data was found for a specific query
"""

pass


class InfluxHandler:
"""
Class for handling influxdb queries
Class for handling influxdb queries.
This requires the influx dbrc mapping to have db name == bucket name
TODO: Implement proper error handling for things like no data available.
"""

@staticmethod
def _gen_headers():
def _gen_headers() -> Dict:
"""
:returns Required headers for proper database querying.
"""
headers = {
"Authorization": f"Token {TEMP_TOKEN}",
"Content-type": "application/json",
}
return headers

# NOTE unused
@staticmethod
def get_bucket_names_and_ids():
def get_measurements(db: str = BUCKET) -> List:
"""
:return:
"""
headers = InfluxHandler._gen_headers()
params = {}
response = requests.get(
f"{INFLUX_DB_URL}/api/v2/buckets", headers=headers, params=params
)

response_json = response.json()
return [
{"name": bucket["name"], "id": bucket["id"]}
for bucket in response_json["buckets"]
if not bucket["name"].startswith("_")
]

@staticmethod
def get_measurements(db=BUCKET):
"""
:param db:
:return:
Get all measurements from the database.
:param db: Name of bucket to fetch data from.
:returns List of all measurements.
"""
headers = InfluxHandler._gen_headers()
params = {
Expand All @@ -73,12 +59,12 @@ def get_measurements(db=BUCKET):
return [measurement["values"][0][0] for measurement in results["series"]]

@staticmethod
def get_fields(measurement, db=BUCKET):
def get_fields(measurement: str, db: str = BUCKET) -> List:
"""
:param measurement:
:param db:
:return:
Get all fields from a measurement.
:param measurement: Measurement to fetch fields from.
:param db: Name of bucket to fetch data from.
:return: List of all fields.
"""
headers = InfluxHandler._gen_headers()
params = {
Expand All @@ -95,16 +81,21 @@ def get_fields(measurement, db=BUCKET):

@staticmethod
def query(
measurement, fields, time_range, db=BUCKET, max_points=8000, ms_resolution=500
):
measurement: str,
fields: List[str],
time_range: Tuple[int, int],
db: str = BUCKET,
max_points: int = 8000,
ms_resolution: int = 100,
) -> Dict:
"""
:param measurement:
:param fields:
:param time_range:
:param db:
:param max_points:
:param ms_resolution:
Make a general query to the database.
:param measurement: Measurement to pull data from.
:param fields: Fields to fetch.
:param time_range: Tuple like (time start, time end) to specify the time interval.
:param db: Name of bucket to fetch data from.
:param max_points: Maximum number of datapoints to fetch.
:param ms_resolution: Minimum time delta required before grabbing a new datapoint.
:return:
"""
headers = InfluxHandler._gen_headers()
Expand All @@ -127,25 +118,25 @@ def query(
values = results["values"]

data = {column: {"time": [], "value": []} for column in columns[1:]}
prevTime = parse(values[0][0])
max_val = 0 # TODO rename
prev_time = parse(values[0][0])
cur_point = 0

for value in values:
nextTime = parse(value[0])
timeDelta = nextTime - prevTime
time_delta = nextTime - prev_time

if max_val > max_points:
if cur_point > max_points:
break

if timeDelta.total_seconds() <= ms_resolution / 1000:
if time_delta.total_seconds() <= ms_resolution / 1000:
continue

for i in range(1, len(value)):
column = columns[i]
data[column]["time"].append(value[0])
data[column]["value"].append(value[i])

prevTime = nextTime
max_val += 1
prev_time = nextTime
cur_point += 1

return data
Empty file.

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit 8f44cf0

Please sign in to comment.