Kvaser bridge instructions #182
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |