Skip to content

Kvaser bridge instructions #182

Kvaser bridge instructions

Kvaser bridge instructions #182

Workflow file for this run

name: Telemetry System CI/CD
on:
workflow_dispatch:
push:
paths:
- 'universal-telemetry-software/**'
- 'pecan/**'
- '.github/workflows/telemetry-ci.yml'
pull_request:
paths:
- 'universal-telemetry-software/**'
- 'pecan/**'
- '.github/workflows/telemetry-ci.yml'
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
telemetry: ${{ steps.filter.outputs.telemetry }}
pecan: ${{ steps.filter.outputs.pecan }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
telemetry:
- 'universal-telemetry-software/**'
pecan:
- 'pecan/**'
# ── Build images once, share via artifact ───────────────────────────────
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Build telemetry image with GHA layer cache (benefits publish jobs too)
- name: Build telemetry image
uses: docker/build-push-action@v5
with:
context: ./universal-telemetry-software
outputs: type=docker,dest=/tmp/telemetry.tar
tags: universal-telemetry:test
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build car-jitsi-client image
run: |
docker build -t car-jitsi-client:test ./universal-telemetry-software/car-jitsi-client
docker save car-jitsi-client:test -o /tmp/jitsi.tar
# Build pecan with the same args used in docker-compose.test.yml
- name: Build pecan image
run: |
docker build \
--build-arg VITE_WS_URL=wss://ws-wfr.0001200.xyz:9443 \
-t pecan:test ./pecan
docker save pecan:test -o /tmp/pecan.tar
- name: Validate compose configurations
working-directory: ./universal-telemetry-software
run: |
docker compose -f docker-compose.yml config --quiet
docker compose -f docker-compose.test.yml config --quiet
docker compose -f docker-compose.prod.yml config --quiet
docker compose -f docker-compose.staging.yml config --quiet
docker compose -f docker-compose.can-test.yml config --quiet
echo "✓ All compose configs valid"
- name: Upload Docker image artifacts
uses: actions/upload-artifact@v4
with:
name: docker-images
path: /tmp/*.tar
retention-days: 1
# ── Unit tests: no containers, fast feedback ─────────────────────────────
unit-tests:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download Docker image artifacts
uses: actions/download-artifact@v4
with:
name: docker-images
path: /tmp
- name: Load Docker images
run: |
docker load -i /tmp/telemetry.tar
docker load -i /tmp/jitsi.tar
- name: Verify car-jitsi-client starts
run: |
docker run --rm --entrypoint node car-jitsi-client:test -e "require('./index.js')" &
PID=$!
sleep 5
kill $PID 2>/dev/null || true
- name: Verify go2rtc config
run: |
docker run --rm -v ${{ github.workspace }}/universal-telemetry-software/go2rtc.yaml:/config/go2rtc.yaml:ro alexxit/go2rtc:latest -c /config/go2rtc.yaml &
PID=$!
sleep 3
kill $PID 2>/dev/null || true
- name: Build and typecheck PECAN
working-directory: ./pecan
run: |
npm ci
npx tsc --noEmit
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Python dependencies
run: |
pip install \
pytest pytest-asyncio pytest-timeout \
cantools influxdb-client \
websockets redis requests
- name: Run unit tests
working-directory: ./universal-telemetry-software
run: |
python -m pytest tests/test_leds.py tests/test_influx_bridge.py tests/test_car_mode.py tests/test_dbc_consistency.py -v
# ── Integration tests: full docker-compose stack ─────────────────────────
integration:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download Docker image artifacts
uses: actions/download-artifact@v4
with:
name: docker-images
path: /tmp
# Loading the tars populates Docker's layer cache so compose --build is instant
- name: Load Docker images
run: |
docker load -i /tmp/telemetry.tar
docker load -i /tmp/pecan.tar
# ── Pull heavy images in parallel ────────────────────────────────────
- name: Pull base images
run: |
docker pull influxdb:3.5.0-core &
docker pull redis:8.2 &
wait
echo "✓ Images pulled"
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Python dependencies
run: |
pip install \
pytest pytest-asyncio pytest-timeout \
cantools influxdb-client \
websockets redis requests \
playwright
- name: Install Playwright browsers
run: playwright install chromium --with-deps
- name: Start integration test environment
working-directory: ./universal-telemetry-software
run: |
docker compose -f docker-compose.test.yml up -d --build
echo "Waiting for all services (InfluxDB3 needs ~15s)..."
sleep 20
- name: Check container health
working-directory: ./universal-telemetry-software
run: |
docker compose -f docker-compose.test.yml ps
docker compose -f docker-compose.test.yml logs --tail 30
- name: Verify containers
run: |
for container in daq-car daq-base daq-car-redis daq-base-redis daq-pecan-test daq-test-influxdb3; do
if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
echo "✓ ${container} running"
else
echo "✗ ${container} NOT running"
docker logs ${container} 2>&1 || true
exit 1
fi
done
- name: Run integration tests
working-directory: ./universal-telemetry-software
run: |
python -m pytest tests/test_integration.py tests/test_websocket_v2.py -v -s --timeout=120
- name: Collect logs on failure
if: failure()
working-directory: ./universal-telemetry-software
run: |
mkdir -p test-logs
docker compose -f docker-compose.test.yml logs --no-color > test-logs/docker-compose.log 2>&1
docker logs daq-car > test-logs/car.log 2>&1 || true
docker logs daq-base > test-logs/base.log 2>&1 || true
docker logs daq-car-redis > test-logs/car-redis.log 2>&1 || true
docker logs daq-base-redis > test-logs/base-redis.log 2>&1 || true
docker logs daq-pecan-test > test-logs/pecan.log 2>&1 || true
docker logs daq-test-influxdb3 > test-logs/influxdb3.log 2>&1 || true
- name: Upload integration test logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: integration-test-logs
path: universal-telemetry-software/test-logs/
retention-days: 7
- name: Cleanup integration test stack
if: always()
working-directory: ./universal-telemetry-software
run: |
docker compose -f docker-compose.test.yml down -v
# ── vCAN pipeline tests: real CAN code path ──────────────────────────────
vcan:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download Docker image artifacts
uses: actions/download-artifact@v4
with:
name: docker-images
path: /tmp
- name: Load telemetry image
run: docker load -i /tmp/telemetry.tar
- name: Pull redis image
run: docker pull redis:8.2
- name: Set up virtual CAN interface
run: |
sudo apt-get install -y linux-modules-extra-$(uname -r)
sudo modprobe can
sudo modprobe can_raw
sudo modprobe vcan
sudo ip link add dev can0 type vcan
sudo ip link set up can0
ip link show can0
echo "✓ vcan interface can0 is UP"
- name: Install can-utils
run: sudo apt-get install -y can-utils
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Python dependencies
run: |
pip install \
pytest pytest-asyncio pytest-timeout \
cantools influxdb-client \
websockets redis requests
- name: Start vCAN test stack
working-directory: ./universal-telemetry-software
run: |
docker compose -f docker-compose.can-test.yml up -d --build
echo "Waiting for telemetry container to start CAN reader..."
sleep 15
- name: Verify can0 visible inside container
run: |
if docker exec daq-can-test ip link show can0 2>&1 | grep -q "can0"; then
echo "✓ can0 is visible inside container"
else
echo "✗ can0 NOT visible inside container"
echo "--- Host interfaces ---"
ip link show
echo "--- Container interfaces ---"
docker exec daq-can-test ip link show 2>&1 || true
exit 1
fi
- name: Verify real CAN mode (not simulation)
run: |
if docker logs daq-can-test 2>&1 | grep -q "CAN Reader started on can0"; then
echo "✓ Container is reading real can0"
else
echo "✗ Container did NOT start real CAN reader"
docker logs daq-can-test 2>&1
exit 1
fi
- name: Run vCAN pipeline tests
working-directory: ./universal-telemetry-software
run: |
python -m pytest tests/test_can_pipeline.py -v -s --timeout=60
- name: Run pecan UI vCAN tests
working-directory: ./pecan
run: |
npm ci
npm run test:ci
- name: Collect vCAN test logs on failure
if: failure()
working-directory: ./universal-telemetry-software
run: |
mkdir -p test-logs
docker logs daq-can-test > test-logs/can-test.log 2>&1 || true
docker logs daq-can-test-redis > test-logs/can-test-redis.log 2>&1 || true
- name: Upload vCAN test logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: vcan-test-logs
path: universal-telemetry-software/test-logs/
retention-days: 7
- name: Cleanup vCAN test stack
if: always()
working-directory: ./universal-telemetry-software
run: |
docker compose -f docker-compose.can-test.yml down -v 2>/dev/null || true
sudo ip link delete can0 2>/dev/null || true
publish-telemetry:
needs: [unit-tests, integration, vcan, detect-changes]
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || (github.event_name != 'pull_request' && needs.detect-changes.outputs.telemetry == 'true')
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Telemetry
id: meta-telemetry
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}/universal-telemetry
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=raw,value=test-latest,enable=${{ github.ref != 'refs/heads/main' }}
type=sha
- name: Build and push Telemetry image
uses: docker/build-push-action@v5
with:
context: ./universal-telemetry-software
file: ./universal-telemetry-software/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta-telemetry.outputs.tags }}
labels: ${{ steps.meta-telemetry.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Extract metadata (tags, labels) for Car Jitsi Client
id: meta-car-jitsi
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}/car-jitsi-client
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=raw,value=test-latest,enable=${{ github.ref != 'refs/heads/main' }}
type=sha
- name: Build and push Car Jitsi Client image
uses: docker/build-push-action@v5
with:
context: ./universal-telemetry-software/car-jitsi-client
file: ./universal-telemetry-software/car-jitsi-client/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta-car-jitsi.outputs.tags }}
labels: ${{ steps.meta-car-jitsi.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
publish-pecan:
needs: [unit-tests, integration, vcan, detect-changes]
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || (github.event_name != 'pull_request' && needs.detect-changes.outputs.pecan == 'true')
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Pecan
id: meta-pecan
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}/pecan
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=raw,value=test-latest,enable=${{ github.ref != 'refs/heads/main' }}
type=sha
- name: Build and push Pecan image
uses: docker/build-push-action@v5
with:
context: ./pecan
file: ./pecan/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta-pecan.outputs.tags }}
labels: ${{ steps.meta-pecan.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-pecan-pages:
needs: [unit-tests, integration, vcan]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: pecan/package-lock.json
- name: Install dependencies
working-directory: ./pecan
run: npm ci
- name: Run tests
working-directory: ./pecan
run: npm run test:ci
- name: Build
working-directory: ./pecan
run: npm run build
env:
VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }}
VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }}
VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}
VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }}
VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }}
VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./pecan/dist
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
deploy-pecan-dev-cloudflare:
needs: [unit-tests, integration, vcan]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/dev'
permissions:
contents: read
deployments: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: pecan/package-lock.json
- name: Install dependencies
working-directory: ./pecan
run: npm ci
- name: Run tests
working-directory: ./pecan
run: npm run test:ci
- name: Build
working-directory: ./pecan
run: npm run build
env:
VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY_DEV }}
VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN_DEV }}
VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID_DEV }}
VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET_DEV }}
VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID_DEV }}
VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID_DEV }}
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: 'pecan-dev'
directory: './pecan/dist'
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
branch: dev