mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-19 00:41:41 +00:00
Add playwright E2E tests (#2174)
This commit is contained in:
43
.github/workflows/build.yml
vendored
43
.github/workflows/build.yml
vendored
@@ -59,6 +59,49 @@ jobs:
|
||||
- name: Build C++ examples
|
||||
working-directory: photonlib-cpp-examples
|
||||
run: ./gradlew build
|
||||
playwright-tests:
|
||||
name: "Playwright E2E tests"
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [validation]
|
||||
steps:
|
||||
# Checkout code.
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Fetch tags
|
||||
run: git fetch --tags --force
|
||||
- name: Install Java 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Install mrcal deps
|
||||
run: sudo apt-get update && sudo apt-get install -y libcholmod3 liblapack3 libsuitesparseconfig5
|
||||
- name: Setup tests
|
||||
working-directory: photon-client
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm test-setup
|
||||
- name: Prebuild Gradle
|
||||
run: ./gradlew photon-targeting:build photon-core:build photon-server:build -x check
|
||||
- name: Run Playwright tests
|
||||
working-directory: photon-client
|
||||
run: pnpm test
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-report
|
||||
path: photon-client/playwright-report/
|
||||
retention-days: 30
|
||||
build-gradle:
|
||||
name: "Gradle Build"
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -146,5 +146,13 @@ networktables.json
|
||||
photon-server/src/main/resources/web/*
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
components.d.ts
|
||||
photon-server/src/main/resources/web/index.html
|
||||
|
||||
# Playwright
|
||||
photon-client/test-results/
|
||||
photon-client/playwright-report/
|
||||
photon-client/blob-report/
|
||||
photon-client/playwright/.cache/
|
||||
photon-client/playwright/.auth/
|
||||
|
||||
8
photon-client/.gitignore
vendored
8
photon-client/.gitignore
vendored
@@ -1,8 +0,0 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
components.d.ts
|
||||
@@ -8,7 +8,7 @@ export default defineConfigWithVueTs(
|
||||
vueTsConfigs.recommended,
|
||||
skipFormattingConfig,
|
||||
{
|
||||
ignores: ["**/dist/**"]
|
||||
ignores: ["**/dist/**", "playwright-report"]
|
||||
},
|
||||
{
|
||||
//extends: ["js/recommended"],
|
||||
|
||||
@@ -9,9 +9,13 @@
|
||||
"build": "vite build",
|
||||
"build-demo": "vite build --mode demo",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
||||
"format": "prettier --write src/",
|
||||
"format": "prettier --write src/ tests/",
|
||||
"lint-ci": "eslint . --max-warnings 0 --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
|
||||
"format-ci": "prettier --check src/"
|
||||
"format-ci": "prettier --check src/",
|
||||
"test": "playwright test",
|
||||
"test-ui": "playwright test --ui",
|
||||
"test-setup": "playwright install --with-deps"
|
||||
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/prompt": "^5.2.6",
|
||||
@@ -28,6 +32,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@types/node": "^22.15.14",
|
||||
"@types/three": "^0.178.0",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
|
||||
83
photon-client/playwright.config.ts
Normal file
83
photon-client/playwright.config.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import path from "path";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// import dotenv from 'dotenv';
|
||||
// import path from 'path';
|
||||
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
globalSetup: "./tests/global-setup",
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: 1,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('')`. */
|
||||
baseURL: "http://localhost:5800",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry"
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] }
|
||||
},
|
||||
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] }
|
||||
},
|
||||
|
||||
{
|
||||
name: "webkit",
|
||||
use: { ...devices["Desktop Safari"] }
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
{
|
||||
name: "Microsoft Edge",
|
||||
use: { ...devices["Desktop Edge"], channel: "msedge" }
|
||||
}
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: process.platform == "win32" ? "" : "./" + "gradlew run",
|
||||
url: "http://localhost:5800",
|
||||
timeout: 300 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
cwd: path.normalize("../")
|
||||
}
|
||||
});
|
||||
38
photon-client/pnpm-lock.yaml
generated
38
photon-client/pnpm-lock.yaml
generated
@@ -45,6 +45,9 @@ importers:
|
||||
'@eslint/js':
|
||||
specifier: ^9.31.0
|
||||
version: 9.31.0
|
||||
'@playwright/test':
|
||||
specifier: ^1.56.1
|
||||
version: 1.56.1
|
||||
'@types/node':
|
||||
specifier: ^22.15.14
|
||||
version: 22.15.14
|
||||
@@ -430,6 +433,11 @@ packages:
|
||||
resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
|
||||
'@playwright/test@1.56.1':
|
||||
resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.19':
|
||||
resolution: {integrity: sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==}
|
||||
|
||||
@@ -1019,6 +1027,11 @@ packages:
|
||||
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@@ -1247,6 +1260,16 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
playwright-core@1.56.1:
|
||||
resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.56.1:
|
||||
resolution: {integrity: sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
postcss-selector-parser@6.1.2:
|
||||
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1759,6 +1782,10 @@ snapshots:
|
||||
|
||||
'@pkgr/core@0.2.4': {}
|
||||
|
||||
'@playwright/test@1.56.1':
|
||||
dependencies:
|
||||
playwright: 1.56.1
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.19': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.40.2':
|
||||
@@ -2399,6 +2426,9 @@ snapshots:
|
||||
hasown: 2.0.2
|
||||
mime-types: 2.1.35
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
@@ -2605,6 +2635,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
playwright-core@1.56.1: {}
|
||||
|
||||
playwright@1.56.1:
|
||||
dependencies:
|
||||
playwright-core: 1.56.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
postcss-selector-parser@6.1.2:
|
||||
dependencies:
|
||||
cssesc: 3.0.0
|
||||
|
||||
@@ -224,6 +224,7 @@ const handleBulkImport = () => {
|
||||
v-model="importVersion"
|
||||
variant="underlined"
|
||||
label="Model Version"
|
||||
data-testid="import-version-select"
|
||||
:items="
|
||||
useSettingsStore().general.supportedBackends?.includes('RKNN')
|
||||
? ['YOLOv5', 'YOLOv8', 'YOLO11']
|
||||
@@ -324,7 +325,7 @@ const handleBulkImport = () => {
|
||||
<th>Info</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody data-testid="model-table">
|
||||
<tr v-for="model in supportedModels" :key="model.modelPath">
|
||||
<td>{{ model.nickname }}</td>
|
||||
<td>{{ model.labels.join(", ") }}</td>
|
||||
|
||||
16
photon-client/tests/fixtures.ts
Normal file
16
photon-client/tests/fixtures.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { test as base } from "@playwright/test";
|
||||
import axios from "axios";
|
||||
|
||||
export const test = base.extend({
|
||||
page: async ({ page }, use) => {
|
||||
// Use the page in the test (no per-test backend reset here)
|
||||
axios.defaults.baseURL = "http://localhost:5800/api/test";
|
||||
await use(page);
|
||||
}
|
||||
});
|
||||
|
||||
test.beforeAll(async () => {
|
||||
console.log("Running before all tests: Resetting backend state...");
|
||||
await axios.post("http://localhost:5800/api/test/resetBackend");
|
||||
await axios.post("http://localhost:5800/api/test/activateTestMode");
|
||||
});
|
||||
7
photon-client/tests/global-setup.ts
Normal file
7
photon-client/tests/global-setup.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
async function globalSetup() {
|
||||
// You can perform global setup tasks here, such as starting a server or setting environment variables
|
||||
const path = await import("path");
|
||||
process.env.TESTS_DIR = path.resolve(process.cwd());
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
79
photon-client/tests/platformDependent/od.spec.ts
Normal file
79
photon-client/tests/platformDependent/od.spec.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { expect } from "@playwright/test";
|
||||
import { test } from "../fixtures";
|
||||
import axios from "axios";
|
||||
import path from "path";
|
||||
|
||||
const fakeModelName = "FAKE-MODEL";
|
||||
const fakeLabels = "test, 1, woof";
|
||||
const newModelName = "foo-bar";
|
||||
const platforms = ["LINUX_RK3588_64", "LINUX_QCS6490"];
|
||||
|
||||
for (const platform of platforms) {
|
||||
test.describe(`Platform: ${platform}`, () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/#/settings");
|
||||
await axios.post("/override/platform", { platform: platform });
|
||||
await page.reload();
|
||||
});
|
||||
|
||||
test("testSettingsPage", async ({ page }) => {
|
||||
if (platform.endsWith("RK3588_64")) {
|
||||
await expect(page.getByRole("main")).toContainText("Linux AARCH 64-bit with RK3588");
|
||||
} else if (platform.endsWith("QCS6490")) {
|
||||
await expect(page.getByRole("main")).toContainText("Linux AARCH 64-bit with QCS6490");
|
||||
}
|
||||
await expect(page.getByText("Object Detection")).toBeVisible();
|
||||
});
|
||||
|
||||
test("Upload model", async ({ page }) => {
|
||||
const testsDir = process.env.TESTS_DIR;
|
||||
if (!testsDir) {
|
||||
throw new Error("TESTS_DIR is not set");
|
||||
}
|
||||
|
||||
await page.getByRole("button", { name: "Import Model" }).click();
|
||||
await page.getByRole("textbox", { name: "Labels" }).fill(fakeLabels);
|
||||
await page.getByRole("spinbutton", { name: "Width" }).fill("640");
|
||||
await page.getByRole("spinbutton", { name: "Height" }).fill("640");
|
||||
await page.getByTestId("import-version-select").click();
|
||||
await page.getByRole("option", { name: "YOLOv8" }).click();
|
||||
|
||||
const modelFile = platform.endsWith("RK3588_64") ? `${fakeModelName}.rknn` : `${fakeModelName}.tflite`;
|
||||
await page
|
||||
.getByRole("button", { name: "Model File Model File" })
|
||||
.setInputFiles(path.join(testsDir, "tests/resources", modelFile));
|
||||
|
||||
await page.getByRole("button", { name: "Import Object Detection Model" }).click();
|
||||
|
||||
await page.goto("/#/settings");
|
||||
const tableRow = page.getByTestId("model-table").locator("tr", { hasText: fakeModelName });
|
||||
|
||||
await expect(tableRow).toBeVisible();
|
||||
await expect(tableRow).toContainText(fakeLabels);
|
||||
});
|
||||
|
||||
test("Rename model", async ({ page }) => {
|
||||
const tableRow = page.getByTestId("model-table").locator("tr", { hasText: fakeModelName });
|
||||
|
||||
await tableRow.getByRole("button", { name: "Rename Model" }).click();
|
||||
await page.getByRole("textbox", { name: "New Name New Name" }).fill(newModelName);
|
||||
await page.getByRole("button", { name: "Rename", exact: true }).click();
|
||||
|
||||
await page.reload();
|
||||
|
||||
const renamedRow = page.getByTestId("model-table").locator("tr", { hasText: newModelName });
|
||||
await expect(renamedRow).toContainText(fakeLabels);
|
||||
});
|
||||
|
||||
test("Delete model", async ({ page }) => {
|
||||
const tableRow = page.getByTestId("model-table").locator("tr", { hasText: newModelName });
|
||||
|
||||
await tableRow.getByRole("button", { name: "Delete Model" }).click();
|
||||
await page.getByRole("button", { name: "Delete model", exact: true }).click();
|
||||
|
||||
await page.reload();
|
||||
const deletedRow = page.getByTestId("model-table").locator("tr", { hasText: newModelName });
|
||||
await expect(deletedRow).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
0
photon-client/tests/resources/FAKE-MODEL.rknn
Normal file
0
photon-client/tests/resources/FAKE-MODEL.rknn
Normal file
0
photon-client/tests/resources/FAKE-MODEL.tflite
Normal file
0
photon-client/tests/resources/FAKE-MODEL.tflite
Normal file
@@ -212,12 +212,23 @@ public class NeuralNetworkModelManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton instance of the NeuralNetworkModelManager
|
||||
* Returns the singleton instance of the NeuralNetworkModelManager. Call getInstance() to use the
|
||||
* default (no reset), or getInstance(true) to reset.
|
||||
*
|
||||
* @return The singleton instance
|
||||
*/
|
||||
public static NeuralNetworkModelManager getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
return getInstance(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton instance of the NeuralNetworkModelManager, optionally resetting it.
|
||||
*
|
||||
* @param reset If true, resets the instance
|
||||
* @return The singleton instance
|
||||
*/
|
||||
public static NeuralNetworkModelManager getInstance(boolean reset) {
|
||||
if (INSTANCE == null || reset) {
|
||||
INSTANCE = new NeuralNetworkModelManager();
|
||||
}
|
||||
return INSTANCE;
|
||||
|
||||
@@ -74,6 +74,12 @@ public class RequestHandler {
|
||||
|
||||
private static final ObjectMapper kObjectMapper = new ObjectMapper();
|
||||
|
||||
private static boolean testMode = false;
|
||||
|
||||
public static void setTestMode(boolean isTestMode) {
|
||||
testMode = isTestMode;
|
||||
}
|
||||
|
||||
private record CommonCameraUniqueName(String cameraUniqueName) {}
|
||||
|
||||
public static void onSettingsImportRequest(Context ctx) {
|
||||
@@ -662,35 +668,36 @@ public class RequestHandler {
|
||||
ModelProperties modelProperties =
|
||||
new ModelProperties(modelPath, nickname, labels, width, height, family, version);
|
||||
|
||||
ObjectDetector objDetector = null;
|
||||
|
||||
try {
|
||||
objDetector =
|
||||
switch (family) {
|
||||
case RUBIK -> new RubikModel(modelProperties).load();
|
||||
case RKNN -> new RknnModel(modelProperties).load();
|
||||
};
|
||||
} catch (RuntimeException e) {
|
||||
ctx.status(400);
|
||||
ctx.result("Failed to load object detection model: " + e.getMessage());
|
||||
if (!testMode) {
|
||||
ObjectDetector objDetector = null;
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(modelPath);
|
||||
} catch (IOException ex) {
|
||||
e.addSuppressed(ex);
|
||||
}
|
||||
objDetector =
|
||||
switch (family) {
|
||||
case RUBIK -> new RubikModel(modelProperties).load();
|
||||
case RKNN -> new RknnModel(modelProperties).load();
|
||||
};
|
||||
} catch (RuntimeException e) {
|
||||
ctx.status(400);
|
||||
ctx.result("Failed to load object detection model: " + e.getMessage());
|
||||
|
||||
logger.error("Failed to load object detection model", e);
|
||||
return;
|
||||
} finally {
|
||||
// this finally block will run regardless of what happens in try/catch
|
||||
// please see https://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
|
||||
// for a summary on how finally works
|
||||
if (objDetector != null) {
|
||||
objDetector.release();
|
||||
try {
|
||||
Files.deleteIfExists(modelPath);
|
||||
} catch (IOException ex) {
|
||||
e.addSuppressed(ex);
|
||||
}
|
||||
|
||||
logger.error("Failed to load object detection model", e);
|
||||
return;
|
||||
} finally {
|
||||
// this finally block will run regardless of what happens in try/catch
|
||||
// please see https://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
|
||||
// for a summary on how finally works
|
||||
if (objDetector != null) {
|
||||
objDetector.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConfigManager.getInstance()
|
||||
.getConfig()
|
||||
.neuralNetworkPropertyManager()
|
||||
|
||||
@@ -162,6 +162,13 @@ public class Server {
|
||||
app.post("/api/objectdetection/rename", RequestHandler::onRenameObjectDetectionModelRequest);
|
||||
app.post("/api/objectdetection/nuke", RequestHandler::onNukeObjectDetectionModelsRequest);
|
||||
|
||||
/* Testing API Events */
|
||||
|
||||
app.post("/api/test/resetBackend", TestRequestHandler::handleResetRequest);
|
||||
|
||||
app.post("/api/test/activateTestMode", TestRequestHandler::testMode);
|
||||
app.post("/api/test/override/platform", TestRequestHandler::handlePlatformOverrideRequest);
|
||||
|
||||
app.start(port);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.server;
|
||||
|
||||
import io.javalin.http.Context;
|
||||
import org.photonvision.common.configuration.ConfigManager;
|
||||
import org.photonvision.common.configuration.NeuralNetworkModelManager;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.file.JacksonUtils;
|
||||
|
||||
public class TestRequestHandler {
|
||||
// Treat all 2XX calls as "INFO"
|
||||
// Treat all 4XX calls as "ERROR"
|
||||
// Treat all 5XX calls as "ERROR"
|
||||
|
||||
static Logger logger = new Logger(TestRequestHandler.class, LogGroup.WebServer);
|
||||
|
||||
public static void handleResetRequest(Context ctx) {
|
||||
logger.info("Resetting Backend");
|
||||
// Reset backend
|
||||
ConfigManager.nukeConfigDirectory();
|
||||
ConfigManager.getInstance().load();
|
||||
}
|
||||
|
||||
private record PlatformOverrideRequest(Platform platform) {}
|
||||
|
||||
public static void handlePlatformOverrideRequest(Context ctx) {
|
||||
try {
|
||||
PlatformOverrideRequest request =
|
||||
JacksonUtils.deserialize(ctx.body(), PlatformOverrideRequest.class);
|
||||
Platform platform = request.platform();
|
||||
logger.info("Overriding platform to: " + platform);
|
||||
|
||||
Platform.overridePlatform(platform);
|
||||
NeuralNetworkModelManager.getInstance(true).extractModels();
|
||||
NeuralNetworkModelManager.getInstance().discoverModels();
|
||||
ctx.status(200);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to parse platform override request: " + e.getMessage());
|
||||
ctx.status(400).result("Invalid request");
|
||||
}
|
||||
}
|
||||
|
||||
public static void testMode(Context ctx) {
|
||||
logger.info("Test mode activated");
|
||||
RequestHandler.setTestMode(true);
|
||||
ctx.status(200).result("Test mode activated");
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,8 @@ public enum Platform {
|
||||
public final boolean isSupported;
|
||||
|
||||
// Set once at init, shouldn't be needed after.
|
||||
private static final Platform currentPlatform = getCurrentPlatform();
|
||||
private static Platform currentPlatform = getCurrentPlatform();
|
||||
private static boolean override = false;
|
||||
|
||||
Platform(
|
||||
String description,
|
||||
@@ -119,6 +120,11 @@ public enum Platform {
|
||||
this.nativeLibraryFolderName = nativeLibFolderName;
|
||||
}
|
||||
|
||||
public static void overridePlatform(Platform platform) {
|
||||
currentPlatform = platform;
|
||||
override = true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// Public API
|
||||
|
||||
@@ -128,14 +134,15 @@ public enum Platform {
|
||||
}
|
||||
|
||||
public static boolean isRK3588() {
|
||||
return Platform.isOrangePi()
|
||||
return currentPlatform == LINUX_RK3588_64
|
||||
|| Platform.isOrangePi()
|
||||
|| Platform.isCoolPi4b()
|
||||
|| Platform.isRock5C()
|
||||
|| fileHasText("/proc/device-tree/compatible", "rk3588");
|
||||
}
|
||||
|
||||
public static boolean isQCS6490() {
|
||||
return isRubik();
|
||||
return currentPlatform == LINUX_QCS6490 || Platform.isRubik();
|
||||
}
|
||||
|
||||
public static boolean isRaspberryPi() {
|
||||
@@ -167,6 +174,11 @@ public enum Platform {
|
||||
return runRobotFile.exists();
|
||||
}
|
||||
|
||||
public static boolean isWindows() {
|
||||
var p = getCurrentPlatform();
|
||||
return (p == WINDOWS_32 || p == WINDOWS_64);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
// Debug info related to unknown platforms for debug help
|
||||
@@ -177,6 +189,10 @@ public enum Platform {
|
||||
private static final String UnknownDeviceModelString = "Unknown";
|
||||
|
||||
public static Platform getCurrentPlatform() {
|
||||
if (override) {
|
||||
return currentPlatform;
|
||||
}
|
||||
|
||||
String OS_NAME;
|
||||
if (Platform.OS_NAME != null) {
|
||||
OS_NAME = Platform.OS_NAME;
|
||||
@@ -319,9 +335,4 @@ public enum Platform {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isWindows() {
|
||||
var p = getCurrentPlatform();
|
||||
return (p == WINDOWS_32 || p == WINDOWS_64);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user