diff --git a/app/.eslintrc.cjs b/app/.eslintrc.cjs
index 0b75758..6e9d20d 100644
--- a/app/.eslintrc.cjs
+++ b/app/.eslintrc.cjs
@@ -1,31 +1,31 @@
/** @type { import("eslint").Linter.Config } */
module.exports = {
- root: true,
- extends: [
- 'eslint:recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:svelte/recommended',
- 'prettier'
- ],
- parser: '@typescript-eslint/parser',
- plugins: ['@typescript-eslint'],
- parserOptions: {
- sourceType: 'module',
- ecmaVersion: 2020,
- extraFileExtensions: ['.svelte']
- },
- env: {
- browser: true,
- es2017: true,
- node: true
- },
- overrides: [
- {
- files: ['*.svelte'],
- parser: 'svelte-eslint-parser',
- parserOptions: {
- parser: '@typescript-eslint/parser'
- }
- }
- ]
-};
+ root: true,
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:svelte/recommended',
+ 'prettier'
+ ],
+ parser: '@typescript-eslint/parser',
+ plugins: ['@typescript-eslint'],
+ parserOptions: {
+ sourceType: 'module',
+ ecmaVersion: 2020,
+ extraFileExtensions: ['.svelte']
+ },
+ env: {
+ browser: true,
+ es2017: true,
+ node: true
+ },
+ overrides: [
+ {
+ files: ['*.svelte'],
+ parser: 'svelte-eslint-parser',
+ parserOptions: {
+ parser: '@typescript-eslint/parser'
+ }
+ }
+ ]
+}
diff --git a/app/.prettierrc b/app/.prettierrc
index 647b80d..d8a02c3 100644
--- a/app/.prettierrc
+++ b/app/.prettierrc
@@ -1,7 +1,7 @@
{
"useTabs": false,
"singleQuote": true,
- "tabWidth": 2,
+ "tabWidth": 4,
"trailingComma": "none",
"arrowParens": "avoid",
"experimentalTernaries": true,
diff --git a/app/.vscode/extensions.json b/app/.vscode/extensions.json
index 11efe25..e228a52 100644
--- a/app/.vscode/extensions.json
+++ b/app/.vscode/extensions.json
@@ -1,3 +1,7 @@
{
- "recommendations": ["svelte.svelte-vscode", "bradlc.vscode-tailwindcss", "esbenp.prettier-vscode"]
+ "recommendations": [
+ "svelte.svelte-vscode",
+ "bradlc.vscode-tailwindcss",
+ "esbenp.prettier-vscode"
+ ]
}
diff --git a/app/env.d.ts b/app/env.d.ts
index fe77695..8c7f1c8 100644
--- a/app/env.d.ts
+++ b/app/env.d.ts
@@ -1,8 +1,8 @@
-declare module "app-env" {
- interface ENV {
- VITE_USE_HOST_NAME: boolean;
- }
+declare module 'app-env' {
+ interface ENV {
+ VITE_USE_HOST_NAME: boolean
+ }
- const appEnv: ENV;
- export default appEnv;
+ const appEnv: ENV
+ export default appEnv
}
diff --git a/app/package.json b/app/package.json
index 5ae481a..496b907 100644
--- a/app/package.json
+++ b/app/package.json
@@ -1,65 +1,65 @@
{
- "name": "spot_micro_controller",
- "version": "0.0.1",
- "private": true,
- "scripts": {
- "dev": "vite dev --host",
- "build": "vite build",
- "build:embedded": "cross-env VITE_USE_HOST_NAME=true vite build",
- "preview": "vite preview",
- "test": "pnpm run test:integration && pnpm run test:unit",
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
- "lint": "prettier --check . && eslint .",
- "format": "prettier --write .",
- "test:integration": "playwright test",
- "test:unit": "vitest"
- },
- "devDependencies": {
- "@iconify-json/mdi": "^1.1.64",
- "@iconify-json/tabler": "^1.1.109",
- "@playwright/test": "^1.49.1",
- "@sveltejs/adapter-static": "^3.0.1",
- "@sveltejs/kit": "^2.5.27",
- "@sveltejs/vite-plugin-svelte": "^5.0.3",
- "@types/eslint": "^8.56.0",
- "@types/three": "^0.162.0",
- "@typescript-eslint/eslint-plugin": "^7.0.0",
- "@typescript-eslint/parser": "^7.0.0",
- "autoprefixer": "^10.4.19",
- "eslint": "^8.56.0",
- "eslint-config-prettier": "^9.1.0",
- "eslint-plugin-svelte": "^2.45.1",
- "jsdom": "^24.0.0",
- "prettier": "^3.1.1",
- "prettier-plugin-svelte": "^3.2.6",
- "svelte": "^5.0.0",
- "svelte-check": "^4.0.0",
- "svelte-focus-trap": "^1.2.0",
- "tailwindcss": "^4.0.12",
- "tslib": "^2.6.1",
- "typescript": "^5.5.0",
- "unplugin-icons": "^0.18.5",
- "vite": "^6.2.1",
- "vitest": "^1.2.0"
- },
- "type": "module",
- "dependencies": {
- "@msgpack/msgpack": "^3.1.2",
- "@niku/vite-env-caster": "^1.0.2",
- "@sveltejs/adapter-auto": "^4.0.0",
- "@tailwindcss/vite": "^4.0.12",
- "chart.js": "^4.4.2",
- "compare-versions": "^6.1.0",
- "cross-env": "^7.0.3",
- "daisyui": "^5.0.0",
- "nipplejs": "^0.10.1",
- "svelte-dnd-list": "^0.1.8",
- "svelte-modals": "^2.0.0",
- "three": "^0.162.0",
- "urdf-loader": "^0.12.1",
- "uzip": "^0.20201231.0",
- "xacro-parser": "^0.3.9"
- },
- "packageManager": "pnpm@9.3.0"
+ "name": "spot_micro_controller",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "dev": "vite dev --host",
+ "build": "vite build",
+ "build:embedded": "cross-env VITE_USE_HOST_NAME=true vite build",
+ "preview": "vite preview",
+ "test": "pnpm run test:integration && pnpm run test:unit",
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
+ "lint": "prettier --check . && eslint .",
+ "format": "prettier --write .",
+ "test:integration": "playwright test",
+ "test:unit": "vitest"
+ },
+ "devDependencies": {
+ "@iconify-json/mdi": "^1.1.64",
+ "@iconify-json/tabler": "^1.1.109",
+ "@playwright/test": "^1.49.1",
+ "@sveltejs/adapter-static": "^3.0.1",
+ "@sveltejs/kit": "^2.5.27",
+ "@sveltejs/vite-plugin-svelte": "^5.0.3",
+ "@types/eslint": "^8.56.0",
+ "@types/three": "^0.162.0",
+ "@typescript-eslint/eslint-plugin": "^7.0.0",
+ "@typescript-eslint/parser": "^7.0.0",
+ "autoprefixer": "^10.4.19",
+ "eslint": "^8.56.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-svelte": "^2.45.1",
+ "jsdom": "^24.0.0",
+ "prettier": "^3.1.1",
+ "prettier-plugin-svelte": "^3.2.6",
+ "svelte": "^5.0.0",
+ "svelte-check": "^4.0.0",
+ "svelte-focus-trap": "^1.2.0",
+ "tailwindcss": "^4.0.12",
+ "tslib": "^2.6.1",
+ "typescript": "^5.5.0",
+ "unplugin-icons": "^0.18.5",
+ "vite": "^6.2.1",
+ "vitest": "^1.2.0"
+ },
+ "type": "module",
+ "dependencies": {
+ "@msgpack/msgpack": "^3.1.2",
+ "@niku/vite-env-caster": "^1.0.2",
+ "@sveltejs/adapter-auto": "^4.0.0",
+ "@tailwindcss/vite": "^4.0.12",
+ "chart.js": "^4.4.2",
+ "compare-versions": "^6.1.0",
+ "cross-env": "^7.0.3",
+ "daisyui": "^5.0.0",
+ "nipplejs": "^0.10.1",
+ "svelte-dnd-list": "^0.1.8",
+ "svelte-modals": "^2.0.0",
+ "three": "^0.162.0",
+ "urdf-loader": "^0.12.1",
+ "uzip": "^0.20201231.0",
+ "xacro-parser": "^0.3.9"
+ },
+ "packageManager": "pnpm@9.3.0"
}
diff --git a/app/playwright.config.ts b/app/playwright.config.ts
index e6e8a23..0f0b8a6 100644
--- a/app/playwright.config.ts
+++ b/app/playwright.config.ts
@@ -1,12 +1,12 @@
-import type { PlaywrightTestConfig } from '@playwright/test';
+import type { PlaywrightTestConfig } from '@playwright/test'
const config: PlaywrightTestConfig = {
- webServer: {
- command: 'pnpm run build && pnpm run preview',
- port: 4173
- },
- testDir: 'tests/integration',
- testMatch: /(.+\.)?(test|spec)\.[jt]s/
-};
+ webServer: {
+ command: 'pnpm run build && pnpm run preview',
+ port: 4173
+ },
+ testDir: 'tests/integration',
+ testMatch: /(.+\.)?(test|spec)\.[jt]s/
+}
-export default config;
+export default config
diff --git a/app/src/app.css b/app/src/app.css
index 5e65e5c..2721cc4 100644
--- a/app/src/app.css
+++ b/app/src/app.css
@@ -23,6 +23,14 @@
--base-content: oklch(0.3 0.012 256);
}
+button {
+ cursor: pointer;
+}
+
+button:disabled {
+ cursor: not-allowed;
+}
+
#nipple_0_0,
#nipple_1_1 {
z-index: 10 !important;
diff --git a/app/src/app.d.ts b/app/src/app.d.ts
index 743f07b..5b99784 100644
--- a/app/src/app.d.ts
+++ b/app/src/app.d.ts
@@ -1,13 +1,13 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
- namespace App {
- // interface Error {}
- // interface Locals {}
- // interface PageData {}
- // interface PageState {}
- // interface Platform {}
- }
+ namespace App {
+ // interface Error {}
+ // interface Locals {}
+ // interface PageData {}
+ // interface PageState {}
+ // interface Platform {}
+ }
}
-export {};
+export {}
diff --git a/app/src/app.html b/app/src/app.html
index 9c7db81..0d164ff 100644
--- a/app/src/app.html
+++ b/app/src/app.html
@@ -1,14 +1,17 @@
-
-
-
-
-
-
- %sveltekit.head%
-
-
- %sveltekit.body%
-
+
+
+
+
+
+
+ %sveltekit.head%
+
+
+ %sveltekit.body%
+
diff --git a/app/src/index.test.ts b/app/src/index.test.ts
deleted file mode 100644
index e07cbbd..0000000
--- a/app/src/index.test.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { describe, it, expect } from 'vitest';
-
-describe('sum test', () => {
- it('adds 1 + 2 to equal 3', () => {
- expect(1 + 2).toBe(3);
- });
-});
diff --git a/app/src/lib/api.ts b/app/src/lib/api.ts
index efcf40f..451f86a 100644
--- a/app/src/lib/api.ts
+++ b/app/src/lib/api.ts
@@ -1,22 +1,22 @@
-import { get } from 'svelte/store';
-import { Err, Ok, type Result } from './utilities';
-import { location } from './stores';
+import { get } from 'svelte/store'
+import { Err, Ok, type Result } from './utilities'
+import { location } from './stores'
export namespace api {
export function get(endpoint: string, params?: RequestInit) {
- return sendRequest(endpoint, 'GET', null, params);
+ return sendRequest(endpoint, 'GET', null, params)
}
export function post(endpoint: string, data?: unknown) {
- return sendRequest(endpoint, 'POST', data);
+ return sendRequest(endpoint, 'POST', data)
}
export function put(endpoint: string, data?: unknown) {
- return sendRequest(endpoint, 'PUT', data);
+ return sendRequest(endpoint, 'PUT', data)
}
export function remove(endpoint: string) {
- return sendRequest(endpoint, 'DELETE');
+ return sendRequest(endpoint, 'DELETE')
}
}
@@ -26,8 +26,8 @@ async function sendRequest(
data?: unknown,
params?: RequestInit
): Promise> {
- endpoint = resolveUrl(endpoint);
- const body = data !== null && typeof data !== 'undefined' ? JSON.stringify(data) : undefined;
+ endpoint = resolveUrl(endpoint)
+ const body = data !== null && typeof data !== 'undefined' ? JSON.stringify(data) : undefined
const request = {
...params,
@@ -38,43 +38,42 @@ async function sendRequest(
Authorization: 'Basic',
'Content-Type': 'application/json'
}
- };
+ }
- let response;
+ let response
try {
- response = await fetch(endpoint, request);
+ response = await fetch(endpoint, request)
} catch (error) {
- return Err.new(new Error(), 'An error has occurred');
+ return Err.new(new Error(), 'An error has occurred')
}
- const isResponseOk = response.status >= 200 && response.status < 400;
+ const isResponseOk = response.status >= 200 && response.status < 400
if (!isResponseOk) {
if (response.status === 401) {
- return Err.new(new ApiError(response), 'User was not authorized');
+ return Err.new(new ApiError(response), 'User was not authorized')
}
- return Err.new(new ApiError(response), 'An error has occurred');
+ return Err.new(new ApiError(response), 'An error has occurred')
}
- const contentType =
- response.headers.get('Content-Type') ?? response.headers.get('Content-Type');
+ const contentType = response.headers.get('Content-Type') ?? response.headers.get('Content-Type')
if (contentType && contentType.includes('application/json')) {
- const data = await response.json();
- return Ok.new(data as TResponse);
+ const data = await response.json()
+ return Ok.new(data as TResponse)
} else {
// Handle empty object as response
- return Ok.new(null as TResponse);
+ return Ok.new(null as TResponse)
}
}
function resolveUrl(url: string): string {
- if (url.startsWith('http') || !get(location)) return url;
- const protocol = window.location.protocol;
- return `${protocol}//${get(location)}${url.startsWith('/') ? '' : '/'}${url}`;
+ if (url.startsWith('http') || !get(location)) return url
+ const protocol = window.location.protocol
+ return `${protocol}//${get(location)}${url.startsWith('/') ? '' : '/'}${url}`
}
export class ApiError extends Error {
constructor(public readonly response: Response) {
- super(`${response.status}`);
+ super(`${response.status}`)
}
}
diff --git a/app/src/lib/components/Collapsible.svelte b/app/src/lib/components/Collapsible.svelte
index 9180622..14ce6c7 100644
--- a/app/src/lib/components/Collapsible.svelte
+++ b/app/src/lib/components/Collapsible.svelte
@@ -1,18 +1,18 @@
diff --git a/app/src/lib/components/ConfirmDialog.svelte b/app/src/lib/components/ConfirmDialog.svelte
index c43ee4f..e1f5705 100644
--- a/app/src/lib/components/ConfirmDialog.svelte
+++ b/app/src/lib/components/ConfirmDialog.svelte
@@ -1,43 +1,48 @@
{#if isOpen}
- {@const SvelteComponent = labels?.confirm.icon}
-
+ {@const SvelteComponent = labels?.confirm.icon}
-
{title}
-
-
{message}
-
-
-
-
-
+ role="dialog"
+ class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center"
+ transition:fly={{ y: 50 }}
+ use:exitBeforeEnter
+ use:focusTrap
+ >
+
+
{title}
+
+
{message}
+
+
+
+
+
+
-
{/if}
diff --git a/app/src/lib/components/GithubUpdateDialog.svelte b/app/src/lib/components/GithubUpdateDialog.svelte
index 7c3e2ad..114e0c8 100644
--- a/app/src/lib/components/GithubUpdateDialog.svelte
+++ b/app/src/lib/components/GithubUpdateDialog.svelte
@@ -1,61 +1,61 @@
{#if isOpen}
@@ -89,8 +89,8 @@
class="btn btn-warning text-warning-content inline-flex flex-none items-center"
disabled={updating}
onclick={() => {
- modals.closeAll();
- location.reload();
+ modals.closeAll()
+ location.reload()
}}
>
Close
- import { focusTrap } from 'svelte-focus-trap';
- import { fly } from 'svelte/transition';
- import { Check } from './icons';
- import { exitBeforeEnter, type ModalProps } from 'svelte-modals';
+ import { focusTrap } from 'svelte-focus-trap'
+ import { fly } from 'svelte/transition'
+ import { Check } from './icons'
+ import { exitBeforeEnter, type ModalProps } from 'svelte-modals'
- let {
- isOpen,
- title,
- message,
- onDismiss,
- labels = {
- dismiss: { label: 'Dismiss', icon: Check },
- },
- }: ModalProps = $props();
+ let {
+ isOpen,
+ title,
+ message,
+ onDismiss,
+ labels = {
+ dismiss: { label: 'Dismiss', icon: Check }
+ }
+ }: ModalProps = $props()
{#if isOpen}
-
-
{title}
-
-
{message}
-
-
-
-
+ role="dialog"
+ class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center"
+ transition:fly={{ y: 50 }}
+ use:exitBeforeEnter
+ use:focusTrap
+ >
+
+
{title}
+
+
{message}
+
+
+
+
+
-
{/if}
diff --git a/app/src/lib/components/OrientationIndicator.svelte b/app/src/lib/components/OrientationIndicator.svelte
index ce1c4c5..04888ff 100644
--- a/app/src/lib/components/OrientationIndicator.svelte
+++ b/app/src/lib/components/OrientationIndicator.svelte
@@ -1,78 +1,78 @@
-
+
diff --git a/app/src/lib/components/SettingsCard.svelte b/app/src/lib/components/SettingsCard.svelte
index 364be63..1a82647 100644
--- a/app/src/lib/components/SettingsCard.svelte
+++ b/app/src/lib/components/SettingsCard.svelte
@@ -1,60 +1,76 @@
{#if collapsible}
-
-
- {@render icon?.()}
- {@render title?.()}
-
-
+ class="bg-base-200 rounded-box relative grid w-full max-w-2xl self-center overflow-hidden shadow-lg"
+ >
+
+
+ {@render icon?.()}
+ {@render title?.()}
+
+
+
+ {#if open}
+
+ {@render children?.()}
+
+ {/if}
- {#if open}
-
- {@render children?.()}
-
- {/if}
-
{:else}
-
-
- {@render icon?.()}
- {@render title?.()}
-
- {@render right?.()}
+ class="bg-base-200 rounded-box relative grid w-full max-w-2xl self-center overflow-hidden shadow-lg"
+ >
+
+
+ {@render icon?.()}
+ {@render title?.()}
+
+ {@render right?.()}
+
+
+ {@render children?.()}
+
-
- {@render children?.()}
-
-
{/if}
diff --git a/app/src/lib/components/Spinner.svelte b/app/src/lib/components/Spinner.svelte
index 36dee22..4a48424 100644
--- a/app/src/lib/components/Spinner.svelte
+++ b/app/src/lib/components/Spinner.svelte
@@ -1,9 +1,8 @@
-
-
Loading...
+
+
Loading...
diff --git a/app/src/lib/components/StatusItem.svelte b/app/src/lib/components/StatusItem.svelte
index e842d03..1174d43 100644
--- a/app/src/lib/components/StatusItem.svelte
+++ b/app/src/lib/components/StatusItem.svelte
@@ -1,45 +1,45 @@
- {#if icon}
-
-
+ {#if icon}
+
+
+
+ {/if}
+
+
{title}
+
{description}
- {/if}
-
-
{title}
-
{description}
-
- {@render children?.()}
+ {@render children?.()}
diff --git a/app/src/lib/components/Stream.svelte b/app/src/lib/components/Stream.svelte
index 1c9589d..e4b51cd 100644
--- a/app/src/lib/components/Stream.svelte
+++ b/app/src/lib/components/Stream.svelte
@@ -1,10 +1,10 @@
diff --git a/app/src/lib/components/Toast.svelte b/app/src/lib/components/Toast.svelte
index bc2eb22..86b924b 100644
--- a/app/src/lib/components/Toast.svelte
+++ b/app/src/lib/components/Toast.svelte
@@ -1,35 +1,37 @@
- {#each $notifications as notification (notification.id)}
- {@const SvelteComponent = icon[notification.type]}
-
-
- {notification.message}
-
- {/each}
+ {#each $notifications as notification (notification.id)}
+ {@const SvelteComponent = icon[notification.type]}
+
+
+ {notification.message}
+
+ {/each}
diff --git a/app/src/lib/components/Visualization.svelte b/app/src/lib/components/Visualization.svelte
index 18b1ee0..f153aac 100644
--- a/app/src/lib/components/Visualization.svelte
+++ b/app/src/lib/components/Visualization.svelte
@@ -1,332 +1,339 @@
diff --git a/app/src/lib/components/input/InputPassword.svelte b/app/src/lib/components/input/InputPassword.svelte
index f0fe528..766fda0 100644
--- a/app/src/lib/components/input/InputPassword.svelte
+++ b/app/src/lib/components/input/InputPassword.svelte
@@ -1,19 +1,19 @@
-
\ No newline at end of file
+
diff --git a/app/src/lib/components/input/VerticalSlider.svelte b/app/src/lib/components/input/VerticalSlider.svelte
index a5248e9..2a528dd 100644
--- a/app/src/lib/components/input/VerticalSlider.svelte
+++ b/app/src/lib/components/input/VerticalSlider.svelte
@@ -1,34 +1,35 @@
+ type="range"
+ style="writing-mode: vertical-lr; direction: rtl"
+ class="cursor-pointer"
+ {min}
+ {max}
+ {step}
+ bind:value
+ {...rest}
+/>
diff --git a/app/src/lib/components/input/index.ts b/app/src/lib/components/input/index.ts
index eb527c5..edf3dce 100644
--- a/app/src/lib/components/input/index.ts
+++ b/app/src/lib/components/input/index.ts
@@ -1,2 +1,2 @@
-export { default as PasswordInput } from './InputPassword.svelte';
-export { default as VerticalSlider } from './VerticalSlider.svelte';
+export { default as PasswordInput } from './InputPassword.svelte'
+export { default as VerticalSlider } from './VerticalSlider.svelte'
diff --git a/app/src/lib/components/layout/Widget.svelte b/app/src/lib/components/layout/Widget.svelte
index b6011d2..6ea7cfe 100644
--- a/app/src/lib/components/layout/Widget.svelte
+++ b/app/src/lib/components/layout/Widget.svelte
@@ -1,9 +1,9 @@
diff --git a/app/src/lib/components/layout/WidgetContainer.svelte b/app/src/lib/components/layout/WidgetContainer.svelte
index 86463c5..a7b8005 100644
--- a/app/src/lib/components/layout/WidgetContainer.svelte
+++ b/app/src/lib/components/layout/WidgetContainer.svelte
@@ -1,40 +1,41 @@
-
- {#each container.widgets as widget, index (widget.id + '-' + index)}
-
- {#if isWidgetConfig(widget)}
- {@const SvelteComponent = WidgetComponents[widget.component]}
-
- {:else if widget.widgets}
-
- {/if}
-
- {#if index !== container.widgets.length - 1}
-
-
- {/if}
- {/each}
-
+
+ {#each container.widgets as widget, index (widget.id + '-' + index)}
+
+ {#if isWidgetConfig(widget)}
+ {@const SvelteComponent = WidgetComponents[widget.component]}
+
+ {:else if widget.widgets}
+
+ {/if}
+
+ {#if index !== container.widgets.length - 1}
+
+ {/if}
+ {/each}
+
diff --git a/app/src/lib/components/menu/GithubButton.svelte b/app/src/lib/components/menu/GithubButton.svelte
index 163b165..28f005b 100644
--- a/app/src/lib/components/menu/GithubButton.svelte
+++ b/app/src/lib/components/menu/GithubButton.svelte
@@ -1,15 +1,15 @@
{#if github.active}
-
+
{/if}
diff --git a/app/src/lib/components/menu/LogoButton.svelte b/app/src/lib/components/menu/LogoButton.svelte
index 7e77f98..c3d5b28 100644
--- a/app/src/lib/components/menu/LogoButton.svelte
+++ b/app/src/lib/components/menu/LogoButton.svelte
@@ -1,14 +1,11 @@
-
-
- {appName}
+
+
+ {appName}
diff --git a/app/src/lib/components/menu/Menu.svelte b/app/src/lib/components/menu/Menu.svelte
index ade0a9f..93b175d 100644
--- a/app/src/lib/components/menu/Menu.svelte
+++ b/app/src/lib/components/menu/Menu.svelte
@@ -1,194 +1,198 @@
-
+
-
+
-
+
-
diff --git a/app/src/lib/components/menu/MenuList.svelte b/app/src/lib/components/menu/MenuList.svelte
index f945375..09debd9 100644
--- a/app/src/lib/components/menu/MenuList.svelte
+++ b/app/src/lib/components/menu/MenuList.svelte
@@ -1,48 +1,54 @@
diff --git a/app/src/lib/components/statusbar/FullscreenButton.svelte b/app/src/lib/components/statusbar/FullscreenButton.svelte
index ea333a0..fbb1624 100644
--- a/app/src/lib/components/statusbar/FullscreenButton.svelte
+++ b/app/src/lib/components/statusbar/FullscreenButton.svelte
@@ -1,10 +1,10 @@
\ No newline at end of file
+
diff --git a/app/src/lib/components/statusbar/RSSIIndicator.svelte b/app/src/lib/components/statusbar/RSSIIndicator.svelte
index 40f0a14..d9f6885 100644
--- a/app/src/lib/components/statusbar/RSSIIndicator.svelte
+++ b/app/src/lib/components/statusbar/RSSIIndicator.svelte
@@ -1,33 +1,33 @@
\ No newline at end of file
+
+
diff --git a/app/src/lib/components/statusbar/SleepButton.svelte b/app/src/lib/components/statusbar/SleepButton.svelte
index 1f75687..05e80a7 100644
--- a/app/src/lib/components/statusbar/SleepButton.svelte
+++ b/app/src/lib/components/statusbar/SleepButton.svelte
@@ -1,13 +1,13 @@
{#if $features.sleep}
diff --git a/app/src/lib/components/statusbar/StopButton.svelte b/app/src/lib/components/statusbar/StopButton.svelte
index d8f5c13..28ff091 100644
--- a/app/src/lib/components/statusbar/StopButton.svelte
+++ b/app/src/lib/components/statusbar/StopButton.svelte
@@ -1,10 +1,9 @@
-
-
\ No newline at end of file
+
diff --git a/app/src/lib/components/statusbar/ThemeButton.svelte b/app/src/lib/components/statusbar/ThemeButton.svelte
index 809d759..b65ed15 100644
--- a/app/src/lib/components/statusbar/ThemeButton.svelte
+++ b/app/src/lib/components/statusbar/ThemeButton.svelte
@@ -1,9 +1,9 @@
\ No newline at end of file
+
diff --git a/app/src/lib/components/statusbar/TopBar.svelte b/app/src/lib/components/statusbar/TopBar.svelte
index 85782c6..b08b9b7 100644
--- a/app/src/lib/components/statusbar/TopBar.svelte
+++ b/app/src/lib/components/statusbar/TopBar.svelte
@@ -1,17 +1,17 @@
-
diff --git a/app/src/lib/components/statusbar/UpdateIndicator.svelte b/app/src/lib/components/statusbar/UpdateIndicator.svelte
index 7dc0ffd..08e27d9 100644
--- a/app/src/lib/components/statusbar/UpdateIndicator.svelte
+++ b/app/src/lib/components/statusbar/UpdateIndicator.svelte
@@ -1,109 +1,111 @@
{#if update}
-
-
-
+
+
+
{/if}
diff --git a/app/src/lib/components/statusbar/ViewSelector.svelte b/app/src/lib/components/statusbar/ViewSelector.svelte
index 9961455..c145643 100644
--- a/app/src/lib/components/statusbar/ViewSelector.svelte
+++ b/app/src/lib/components/statusbar/ViewSelector.svelte
@@ -1,6 +1,6 @@
-
v.name)} />
\ No newline at end of file
+ v.name)} />
diff --git a/app/src/lib/components/statusbar/statusbar.svelte b/app/src/lib/components/statusbar/statusbar.svelte
index 61035cf..a724ed2 100644
--- a/app/src/lib/components/statusbar/statusbar.svelte
+++ b/app/src/lib/components/statusbar/statusbar.svelte
@@ -1,38 +1,38 @@
-
-
- {#if page.data.title === 'Controller'}
-
- {:else}
-
{page.data.title}
- {/if}
-
+
+
+ {#if page.data.title === 'Controller'}
+
+ {:else}
+
{page.data.title}
+ {/if}
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/app/src/lib/components/toasts/Toast.svelte b/app/src/lib/components/toasts/Toast.svelte
index 3c95016..497072b 100644
--- a/app/src/lib/components/toasts/Toast.svelte
+++ b/app/src/lib/components/toasts/Toast.svelte
@@ -1,35 +1,37 @@
- {#each $notifications as notification (notification.id)}
- {@const SvelteComponent = icon[notification.type]}
-
-
- {notification.message}
-
- {/each}
+ {#each $notifications as notification (notification.id)}
+ {@const SvelteComponent = icon[notification.type]}
+
+
+ {notification.message}
+
+ {/each}
diff --git a/app/src/lib/components/toasts/notifications.ts b/app/src/lib/components/toasts/notifications.ts
index 3131abb..57e28e5 100644
--- a/app/src/lib/components/toasts/notifications.ts
+++ b/app/src/lib/components/toasts/notifications.ts
@@ -3,40 +3,40 @@ import { writable, derived, type Writable } from 'svelte/store'
type StateType = 'info' | 'success' | 'warning' | 'error'
type State = {
- id: string
- type: StateType
- message: string
+ id: string
+ type: StateType
+ message: string
}
function createNotificationStore() {
- const state: State[] = []
- const notifications = writable(state)
- const { subscribe } = notifications
+ const state: State[] = []
+ const notifications = writable(state)
+ const { subscribe } = notifications
- function send(message: string, type: StateType = 'info', timeout: number) {
- const id = generateId()
- setTimeout(() => {
- notifications.update(state => {
- return state.filter(n => n.id !== id)
- })
- }, timeout)
- notifications.update(state => {
- return [...state, { id, type, message }]
- })
- }
+ function send(message: string, type: StateType = 'info', timeout: number) {
+ const id = generateId()
+ setTimeout(() => {
+ notifications.update(state => {
+ return state.filter(n => n.id !== id)
+ })
+ }, timeout)
+ notifications.update(state => {
+ return [...state, { id, type, message }]
+ })
+ }
- return {
- subscribe,
- send,
- error: (msg: string, timeout: number = 4000) => send(msg, 'error', timeout),
- warning: (msg: string, timeout: number = 4000) => send(msg, 'warning', timeout),
- info: (msg: string, timeout: number = 4000) => send(msg, 'info', timeout),
- success: (msg: string, timeout: number = 4000) => send(msg, 'success', timeout)
- }
+ return {
+ subscribe,
+ send,
+ error: (msg: string, timeout: number = 4000) => send(msg, 'error', timeout),
+ warning: (msg: string, timeout: number = 4000) => send(msg, 'warning', timeout),
+ info: (msg: string, timeout: number = 4000) => send(msg, 'info', timeout),
+ success: (msg: string, timeout: number = 4000) => send(msg, 'success', timeout)
+ }
}
function generateId() {
- return '_' + Math.random().toString(36).substr(2, 9)
+ return '_' + Math.random().toString(36).substr(2, 9)
}
export const notifications = createNotificationStore()
diff --git a/app/src/lib/components/widget/ChartWidget.svelte b/app/src/lib/components/widget/ChartWidget.svelte
index 71d47a1..dd5fe92 100644
--- a/app/src/lib/components/widget/ChartWidget.svelte
+++ b/app/src/lib/components/widget/ChartWidget.svelte
@@ -1,101 +1,102 @@
diff --git a/app/src/lib/components/widget/Selector.svelte b/app/src/lib/components/widget/Selector.svelte
index b4666f3..7e3d606 100644
--- a/app/src/lib/components/widget/Selector.svelte
+++ b/app/src/lib/components/widget/Selector.svelte
@@ -1,19 +1,20 @@
diff --git a/app/src/lib/gait.ts b/app/src/lib/gait.ts
index b6fb846..726e3db 100644
--- a/app/src/lib/gait.ts
+++ b/app/src/lib/gait.ts
@@ -3,423 +3,423 @@ import type { body_state_t } from './kinematic'
import { currentKinematic } from './stores/featureFlags'
export interface gait_state_t {
- step_height: number
- step_x: number
- step_z: number
- step_angle: number
- step_velocity: number
- step_depth: number
+ step_height: number
+ step_x: number
+ step_z: number
+ step_angle: number
+ step_velocity: number
+ step_depth: number
}
export interface ControllerCommand {
- lx: number
- ly: number
- rx: number
- ry: number
- h: number
- s: number
- s1: number
+ lx: number
+ ly: number
+ rx: number
+ ry: number
+ h: number
+ s: number
+ s1: number
}
export abstract class GaitState {
- protected abstract name: string
+ protected abstract name: string
- protected dt = 0.02
- protected body_state!: body_state_t
- protected gait_state: gait_state_t = {
- step_height: 0.4,
- step_x: 0,
- step_z: 0,
- step_angle: 0,
- step_velocity: 1,
- step_depth: 0.002
- }
-
- public get default_feet_pos() {
- return get(currentKinematic).getDefaultFeetPos()
- }
-
- protected get default_height() {
- return 0.5
- }
-
- begin() {
- console.log('Starting', this.name)
- }
- end() {
- console.log('Ending', this.name)
- }
- step(body_state: body_state_t, command: ControllerCommand, dt: number = 0.02) {
- this.map_command(command)
- this.body_state = body_state
- this.dt = dt / 1000
- return body_state
- }
-
- map_command(command: ControllerCommand) {
- const newCommand = {
- step_height: 0.4 + (command.s1 + 1) / 2,
- step_x: command.ly,
- step_z: -command.lx,
- step_velocity: command.s,
- step_angle: command.rx,
- step_depth: 0.002
+ protected dt = 0.02
+ protected body_state!: body_state_t
+ protected gait_state: gait_state_t = {
+ step_height: 0.4,
+ step_x: 0,
+ step_z: 0,
+ step_angle: 0,
+ step_velocity: 1,
+ step_depth: 0.002
}
- this.gait_state = newCommand
- }
+ public get default_feet_pos() {
+ return get(currentKinematic).getDefaultFeetPos()
+ }
+
+ protected get default_height() {
+ return 0.5
+ }
+
+ begin() {
+ console.log('Starting', this.name)
+ }
+ end() {
+ console.log('Ending', this.name)
+ }
+ step(body_state: body_state_t, command: ControllerCommand, dt: number = 0.02) {
+ this.map_command(command)
+ this.body_state = body_state
+ this.dt = dt / 1000
+ return body_state
+ }
+
+ map_command(command: ControllerCommand) {
+ const newCommand = {
+ step_height: 0.4 + (command.s1 + 1) / 2,
+ step_x: command.ly,
+ step_z: -command.lx,
+ step_velocity: command.s,
+ step_angle: command.rx,
+ step_depth: 0.002
+ }
+
+ this.gait_state = newCommand
+ }
}
export class IdleState extends GaitState {
- protected name = 'Idle'
+ protected name = 'Idle'
}
export class CalibrationState extends GaitState {
- protected name = 'Calibration'
+ protected name = 'Calibration'
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- step(body_state: body_state_t, _command: ControllerCommand) {
- body_state.omega = 0
- body_state.phi = 0
- body_state.psi = 0
- body_state.xm = 0
- body_state.ym = this.default_height * 10
- body_state.zm = 0
- body_state.feet = this.default_feet_pos
- return body_state
- }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ step(body_state: body_state_t, _command: ControllerCommand) {
+ body_state.omega = 0
+ body_state.phi = 0
+ body_state.psi = 0
+ body_state.xm = 0
+ body_state.ym = this.default_height * 10
+ body_state.zm = 0
+ body_state.feet = this.default_feet_pos
+ return body_state
+ }
}
export class RestState extends GaitState {
- protected name = 'Rest'
+ protected name = 'Rest'
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- step(body_state: body_state_t, _command: ControllerCommand) {
- body_state.omega = 0
- body_state.phi = 0
- body_state.psi = 0
- body_state.xm = 0
- body_state.ym = this.default_height / 2
- body_state.zm = 0
- body_state.feet = this.default_feet_pos
- return body_state
- }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ step(body_state: body_state_t, _command: ControllerCommand) {
+ body_state.omega = 0
+ body_state.phi = 0
+ body_state.psi = 0
+ body_state.xm = 0
+ body_state.ym = this.default_height / 2
+ body_state.zm = 0
+ body_state.feet = this.default_feet_pos
+ return body_state
+ }
}
export class StandState extends GaitState {
- protected name = 'Stand'
+ protected name = 'Stand'
- step(body_state: body_state_t, command: ControllerCommand) {
- body_state.omega = 0
- body_state.phi = command.rx * 10 * (Math.PI / 2)
- body_state.psi = command.ry * 10 * (Math.PI / 2)
- body_state.xm = command.ly / 4
- body_state.zm = command.lx / 4
- body_state.feet = this.default_feet_pos
- return body_state
- }
+ step(body_state: body_state_t, command: ControllerCommand) {
+ body_state.omega = 0
+ body_state.phi = command.rx * 10 * (Math.PI / 2)
+ body_state.psi = command.ry * 10 * (Math.PI / 2)
+ body_state.xm = command.ly / 4
+ body_state.zm = command.lx / 4
+ body_state.feet = this.default_feet_pos
+ return body_state
+ }
}
export class BezierState extends GaitState {
- protected name = 'Bezier'
- protected phase = 0
- protected phase_num = 0
- protected step_length = 0
- protected stand_offset = 0.85
- protected mode: 'crawl' | 'trot' = 'trot'
- protected speed_factor = 1
- offset = [0, 0.5, 0.75, 0.25]
+ protected name = 'Bezier'
+ protected phase = 0
+ protected phase_num = 0
+ protected step_length = 0
+ protected stand_offset = 0.85
+ protected mode: 'crawl' | 'trot' = 'trot'
+ protected speed_factor = 1
+ offset = [0, 0.5, 0.75, 0.25]
- protected shift_start_pos = { x: 0, z: 0 }
- protected shift_target_pos = { x: 0, z: 0 }
- protected shift_start_time = 0
- protected current_shift_leg = -1
+ protected shift_start_pos = { x: 0, z: 0 }
+ protected shift_target_pos = { x: 0, z: 0 }
+ protected shift_start_time = 0
+ protected current_shift_leg = -1
- constructor() {
- super()
- this.set_mode(this.mode)
- }
-
- begin() {
- super.begin()
- }
-
- set_mode(mode: 'crawl' | 'trot', duty?: number, order?: [number, number, number, number]) {
- console.log('BezierState set_mode', mode)
-
- this.mode = mode
- if (mode === 'crawl') {
- this.speed_factor = 0.5
- this.stand_offset = duty ?? 0.85
- const o = order ?? [3, 0, 2, 1]
- const base = [0, 0.25, 0.5, 0.75]
- const offsets = new Array(4).fill(0)
- for (let i = 0; i < 4; i++) offsets[o[i]] = base[i]
- this.offset = offsets
- } else {
- this.speed_factor = 2
- this.stand_offset = duty ?? 0.6
- this.offset = order ? (order.map(v => v % 1) as number[]) : [0, 0.5, 0.5, 0]
+ constructor() {
+ super()
+ this.set_mode(this.mode)
}
- }
- end() {
- super.end()
- }
-
- step(body_state: body_state_t, command: ControllerCommand, dt: number = 0.02) {
- super.step(body_state, command, dt)
- this.step_length = Math.sqrt(this.gait_state.step_x ** 2 + this.gait_state.step_z ** 2)
- if (this.gait_state.step_x < 0) this.step_length = -this.step_length
- this.update_phase()
- this.update_body_position()
- this.update_feet_positions()
- return this.body_state
- }
-
- update_phase() {
- const m = this.gait_state
- if (m.step_x === 0 && m.step_z === 0 && m.step_angle === 0) {
- this.phase = 0
- return
+ begin() {
+ super.begin()
}
- this.phase += this.dt * m.step_velocity * this.speed_factor
- if (this.phase >= 1) {
- this.phase_num = (this.phase_num + 1) % 2
- this.phase = 0
- }
- }
- update_body_position() {
- const m = this.gait_state
- const moving = m.step_x !== 0 || m.step_z !== 0 || m.step_angle !== 0
- if (!moving) return
+ set_mode(mode: 'crawl' | 'trot', duty?: number, order?: [number, number, number, number]) {
+ console.log('BezierState set_mode', mode)
- if (this.mode !== 'crawl') return
-
- const { stance, swing, next_swing, time_to_lift } = this.get_leg_states()
-
- if (stance.length >= 3 && swing.length === 0 && next_swing !== -1) {
- if (this.current_shift_leg !== next_swing) {
- this.current_shift_leg = next_swing
- this.shift_start_pos.x = this.body_state.xm
- this.shift_start_pos.z = this.body_state.zm
-
- const remaining_legs = stance.filter(leg => leg !== next_swing)
- const target = this.stance_centroid(remaining_legs)
- this.shift_target_pos.x = target[0]
- this.shift_target_pos.z = target[2]
-
- this.shift_start_time = time_to_lift
- }
-
- const total_time = this.shift_start_time
- const progress = total_time > 0 ? 1 - time_to_lift / total_time : 1
- const smooth_progress = this.smoothstep01(Math.max(0, Math.min(1, progress)))
-
- this.body_state.xm = this.lerp(
- this.shift_start_pos.x,
- this.shift_target_pos.x,
- smooth_progress
- )
- this.body_state.zm = this.lerp(
- this.shift_start_pos.z,
- this.shift_target_pos.z,
- smooth_progress
- )
- }
- }
-
- protected lerp(a: number, b: number, t: number): number {
- return a + (b - a) * t
- }
-
- protected stance_centroid(legs: number[]): number[] {
- if (legs.length === 0) return [this.body_state.xm, 0, this.body_state.zm]
-
- let sx = 0,
- sz = 0
- for (const i of legs) {
- sx += this.body_state.feet[i][0]
- sz += this.body_state.feet[i][2]
- }
- return [sx / legs.length, 0, sz / legs.length]
- }
-
- protected get_leg_states(): {
- stance: number[]
- swing: number[]
- next_swing: number
- time_to_lift: number
- } {
- const stance: number[] = []
- const swing: number[] = []
- let next_swing = -1
- let min_time_to_swing = Infinity
-
- for (let i = 0; i < 4; i++) {
- let phase = this.phase + this.offset[i]
- if (phase >= 1) phase -= 1
-
- if (phase <= this.stand_offset) {
- stance.push(i)
- const time_to_swing = this.stand_offset - phase
- if (time_to_swing < min_time_to_swing) {
- min_time_to_swing = time_to_swing
- next_swing = i
+ this.mode = mode
+ if (mode === 'crawl') {
+ this.speed_factor = 0.5
+ this.stand_offset = duty ?? 0.85
+ const o = order ?? [3, 0, 2, 1]
+ const base = [0, 0.25, 0.5, 0.75]
+ const offsets = new Array(4).fill(0)
+ for (let i = 0; i < 4; i++) offsets[o[i]] = base[i]
+ this.offset = offsets
+ } else {
+ this.speed_factor = 2
+ this.stand_offset = duty ?? 0.6
+ this.offset = order ? (order.map(v => v % 1) as number[]) : [0, 0.5, 0.5, 0]
}
- } else {
- swing.push(i)
- }
}
- return { stance, swing, next_swing, time_to_lift: min_time_to_swing }
- }
+ end() {
+ super.end()
+ }
- protected smoothstep01(t: number): number {
- const x = Math.max(0, Math.min(1, t))
- return x * x * (3 - 2 * x)
- }
+ step(body_state: body_state_t, command: ControllerCommand, dt: number = 0.02) {
+ super.step(body_state, command, dt)
+ this.step_length = Math.sqrt(this.gait_state.step_x ** 2 + this.gait_state.step_z ** 2)
+ if (this.gait_state.step_x < 0) this.step_length = -this.step_length
+ this.update_phase()
+ this.update_body_position()
+ this.update_feet_positions()
+ return this.body_state
+ }
- update_feet_positions() {
- for (let i = 0; i < 4; i++) this.body_state.feet[i] = this.update_foot_position(i)
- }
+ update_phase() {
+ const m = this.gait_state
+ if (m.step_x === 0 && m.step_z === 0 && m.step_angle === 0) {
+ this.phase = 0
+ return
+ }
+ this.phase += this.dt * m.step_velocity * this.speed_factor
+ if (this.phase >= 1) {
+ this.phase_num = (this.phase_num + 1) % 2
+ this.phase = 0
+ }
+ }
- update_foot_position(index: number): number[] {
- let phase = this.phase + this.offset[index]
- if (phase >= 1) phase -= 1
- this.body_state.feet[index][0] = this.default_feet_pos[index][0]
- this.body_state.feet[index][1] = this.default_feet_pos[index][1]
- this.body_state.feet[index][2] = this.default_feet_pos[index][2]
- return phase <= this.stand_offset ?
- this.stand_controller(index, phase / this.stand_offset)
- : this.swing_controller(index, (phase - this.stand_offset) / (1 - this.stand_offset))
- }
+ update_body_position() {
+ const m = this.gait_state
+ const moving = m.step_x !== 0 || m.step_z !== 0 || m.step_angle !== 0
+ if (!moving) return
- stand_controller(index: number, phase: number) {
- const depth = this.gait_state.step_depth
- return this.controller(index, phase, stance_curve, depth)
- }
+ if (this.mode !== 'crawl') return
- swing_controller(index: number, phase: number) {
- const height = this.gait_state.step_height
- return this.controller(index, phase, bezier_curve, height)
- }
+ const { stance, swing, next_swing, time_to_lift } = this.get_leg_states()
- controller(
- index: number,
- phase: number,
- controller: (length: number, angle: number, ...args: number[]) => number[],
- ...args: number[]
- ) {
- let length = this.step_length / 2
- let angle = Math.atan2(this.gait_state.step_z, this.step_length) * 2
- const delta_pos = controller(length, angle, ...args, phase)
+ if (stance.length >= 3 && swing.length === 0 && next_swing !== -1) {
+ if (this.current_shift_leg !== next_swing) {
+ this.current_shift_leg = next_swing
+ this.shift_start_pos.x = this.body_state.xm
+ this.shift_start_pos.z = this.body_state.zm
- length = this.gait_state.step_angle * 2
- angle = yawArc(this.default_feet_pos[index], this.body_state.feet[index])
+ const remaining_legs = stance.filter(leg => leg !== next_swing)
+ const target = this.stance_centroid(remaining_legs)
+ this.shift_target_pos.x = target[0]
+ this.shift_target_pos.z = target[2]
- const delta_rot = controller(length, angle, ...args, phase)
+ this.shift_start_time = time_to_lift
+ }
- this.body_state.feet[index][0] += delta_pos[0] + delta_rot[0] * 0.2
- this.body_state.feet[index][2] += delta_pos[2] + delta_rot[2] * 0.2
- if (this.gait_state.step_x || this.gait_state.step_z || this.gait_state.step_angle)
- this.body_state.feet[index][1] += delta_pos[1] + delta_rot[1] * 0.2
+ const total_time = this.shift_start_time
+ const progress = total_time > 0 ? 1 - time_to_lift / total_time : 1
+ const smooth_progress = this.smoothstep01(Math.max(0, Math.min(1, progress)))
- return this.body_state.feet[index]
- }
+ this.body_state.xm = this.lerp(
+ this.shift_start_pos.x,
+ this.shift_target_pos.x,
+ smooth_progress
+ )
+ this.body_state.zm = this.lerp(
+ this.shift_start_pos.z,
+ this.shift_target_pos.z,
+ smooth_progress
+ )
+ }
+ }
+
+ protected lerp(a: number, b: number, t: number): number {
+ return a + (b - a) * t
+ }
+
+ protected stance_centroid(legs: number[]): number[] {
+ if (legs.length === 0) return [this.body_state.xm, 0, this.body_state.zm]
+
+ let sx = 0,
+ sz = 0
+ for (const i of legs) {
+ sx += this.body_state.feet[i][0]
+ sz += this.body_state.feet[i][2]
+ }
+ return [sx / legs.length, 0, sz / legs.length]
+ }
+
+ protected get_leg_states(): {
+ stance: number[]
+ swing: number[]
+ next_swing: number
+ time_to_lift: number
+ } {
+ const stance: number[] = []
+ const swing: number[] = []
+ let next_swing = -1
+ let min_time_to_swing = Infinity
+
+ for (let i = 0; i < 4; i++) {
+ let phase = this.phase + this.offset[i]
+ if (phase >= 1) phase -= 1
+
+ if (phase <= this.stand_offset) {
+ stance.push(i)
+ const time_to_swing = this.stand_offset - phase
+ if (time_to_swing < min_time_to_swing) {
+ min_time_to_swing = time_to_swing
+ next_swing = i
+ }
+ } else {
+ swing.push(i)
+ }
+ }
+
+ return { stance, swing, next_swing, time_to_lift: min_time_to_swing }
+ }
+
+ protected smoothstep01(t: number): number {
+ const x = Math.max(0, Math.min(1, t))
+ return x * x * (3 - 2 * x)
+ }
+
+ update_feet_positions() {
+ for (let i = 0; i < 4; i++) this.body_state.feet[i] = this.update_foot_position(i)
+ }
+
+ update_foot_position(index: number): number[] {
+ let phase = this.phase + this.offset[index]
+ if (phase >= 1) phase -= 1
+ this.body_state.feet[index][0] = this.default_feet_pos[index][0]
+ this.body_state.feet[index][1] = this.default_feet_pos[index][1]
+ this.body_state.feet[index][2] = this.default_feet_pos[index][2]
+ return phase <= this.stand_offset ?
+ this.stand_controller(index, phase / this.stand_offset)
+ : this.swing_controller(index, (phase - this.stand_offset) / (1 - this.stand_offset))
+ }
+
+ stand_controller(index: number, phase: number) {
+ const depth = this.gait_state.step_depth
+ return this.controller(index, phase, stance_curve, depth)
+ }
+
+ swing_controller(index: number, phase: number) {
+ const height = this.gait_state.step_height
+ return this.controller(index, phase, bezier_curve, height)
+ }
+
+ controller(
+ index: number,
+ phase: number,
+ controller: (length: number, angle: number, ...args: number[]) => number[],
+ ...args: number[]
+ ) {
+ let length = this.step_length / 2
+ let angle = Math.atan2(this.gait_state.step_z, this.step_length) * 2
+ const delta_pos = controller(length, angle, ...args, phase)
+
+ length = this.gait_state.step_angle * 2
+ angle = yawArc(this.default_feet_pos[index], this.body_state.feet[index])
+
+ const delta_rot = controller(length, angle, ...args, phase)
+
+ this.body_state.feet[index][0] += delta_pos[0] + delta_rot[0] * 0.2
+ this.body_state.feet[index][2] += delta_pos[2] + delta_rot[2] * 0.2
+ if (this.gait_state.step_x || this.gait_state.step_z || this.gait_state.step_angle)
+ this.body_state.feet[index][1] += delta_pos[1] + delta_rot[1] * 0.2
+
+ return this.body_state.feet[index]
+ }
}
const stance_curve = (length: number, angle: number, depth: number, phase: number): number[] => {
- const X_POLAR = Math.cos(angle)
- const Y_POLAR = Math.sin(angle)
+ const X_POLAR = Math.cos(angle)
+ const Y_POLAR = Math.sin(angle)
- const step = length * (1 - 2 * phase)
- const X = step * X_POLAR
- const Z = step * Y_POLAR
- let Y = 0
- if (length !== 0) Y = -depth * Math.cos((Math.PI * (X + Y)) / (2 * length))
- return [X, Y, Z]
+ const step = length * (1 - 2 * phase)
+ const X = step * X_POLAR
+ const Z = step * Y_POLAR
+ let Y = 0
+ if (length !== 0) Y = -depth * Math.cos((Math.PI * (X + Y)) / (2 * length))
+ return [X, Y, Z]
}
const yawArc = (default_foot_pos: number[], current_foot_pos: number[]): number => {
- const foot_mag = Math.sqrt(default_foot_pos[0] ** 2 + default_foot_pos[2] ** 2)
- const foot_dir = Math.atan2(default_foot_pos[2], default_foot_pos[0])
- const offsets = [
- current_foot_pos[0] - default_foot_pos[0],
- current_foot_pos[2] - default_foot_pos[2],
- current_foot_pos[1] - default_foot_pos[1]
- ]
- const offset_mag = Math.sqrt(offsets[0] ** 2 + offsets[2] ** 2)
- const offset_mod = Math.atan2(offset_mag, foot_mag)
+ const foot_mag = Math.sqrt(default_foot_pos[0] ** 2 + default_foot_pos[2] ** 2)
+ const foot_dir = Math.atan2(default_foot_pos[2], default_foot_pos[0])
+ const offsets = [
+ current_foot_pos[0] - default_foot_pos[0],
+ current_foot_pos[2] - default_foot_pos[2],
+ current_foot_pos[1] - default_foot_pos[1]
+ ]
+ const offset_mag = Math.sqrt(offsets[0] ** 2 + offsets[2] ** 2)
+ const offset_mod = Math.atan2(offset_mag, foot_mag)
- return Math.PI / 2.0 + foot_dir + offset_mod
+ return Math.PI / 2.0 + foot_dir + offset_mod
}
const bezier_curve = (length: number, angle: number, height: number, phase: number): number[] => {
- const control_points = get_control_points(length, angle, height)
- const n = control_points.length - 1
+ const control_points = get_control_points(length, angle, height)
+ const n = control_points.length - 1
- const point = [0, 0, 0]
- for (let i = 0; i <= n; i++) {
- const bernstein_poly = comb(n, i) * Math.pow(phase, i) * Math.pow(1 - phase, n - i)
- point[0] += bernstein_poly * control_points[i][0]
- point[1] += bernstein_poly * control_points[i][1]
- point[2] += bernstein_poly * control_points[i][2]
- }
- return point
+ const point = [0, 0, 0]
+ for (let i = 0; i <= n; i++) {
+ const bernstein_poly = comb(n, i) * Math.pow(phase, i) * Math.pow(1 - phase, n - i)
+ point[0] += bernstein_poly * control_points[i][0]
+ point[1] += bernstein_poly * control_points[i][1]
+ point[2] += bernstein_poly * control_points[i][2]
+ }
+ return point
}
const get_control_points = (length: number, angle: number, height: number): number[][] => {
- const X_POLAR = Math.cos(angle)
- const Z_POLAR = Math.sin(angle)
+ const X_POLAR = Math.cos(angle)
+ const Z_POLAR = Math.sin(angle)
- const STEP = [
- -length,
- -length * 1.4,
- -length * 1.5,
- -length * 1.5,
- -length * 1.5,
- 0.0,
- 0.0,
- 0.0,
- length * 1.5,
- length * 1.5,
- length * 1.4,
- length
- ]
+ const STEP = [
+ -length,
+ -length * 1.4,
+ -length * 1.5,
+ -length * 1.5,
+ -length * 1.5,
+ 0.0,
+ 0.0,
+ 0.0,
+ length * 1.5,
+ length * 1.5,
+ length * 1.4,
+ length
+ ]
- const Y = [
- 0.0,
- 0.0,
- height * 0.9,
- height * 0.9,
- height * 0.9,
- height * 0.9,
- height * 0.9,
- height * 1.1,
- height * 1.1,
- height * 1.1,
- 0.0,
- 0.0
- ]
+ const Y = [
+ 0.0,
+ 0.0,
+ height * 0.9,
+ height * 0.9,
+ height * 0.9,
+ height * 0.9,
+ height * 0.9,
+ height * 1.1,
+ height * 1.1,
+ height * 1.1,
+ 0.0,
+ 0.0
+ ]
- const control_points: number[][] = []
+ const control_points: number[][] = []
- for (let i = 0; i < STEP.length; i++) {
- const X = STEP[i] * X_POLAR
- const Z = STEP[i] * Z_POLAR
- control_points.push([X, Y[i], Z])
- }
+ for (let i = 0; i < STEP.length; i++) {
+ const X = STEP[i] * X_POLAR
+ const Z = STEP[i] * Z_POLAR
+ control_points.push([X, Y[i], Z])
+ }
- return control_points
+ return control_points
}
const comb = (n: number, k: number): number => {
- if (k < 0 || k > n) return 0
- if (k === 0 || k === n) return 1
- k = Math.min(k, n - k)
- let c = 1
- for (let i = 0; i < k; i++) c = (c * (n - i)) / (i + 1)
- return c
+ if (k < 0 || k > n) return 0
+ if (k === 0 || k === n) return 1
+ k = Math.min(k, n - k)
+ let c = 1
+ for (let i = 0; i < k; i++) c = (c * (n - i)) / (i + 1)
+ return c
}
diff --git a/app/src/lib/kinematic.ts b/app/src/lib/kinematic.ts
index ec26baa..9fa3418 100644
--- a/app/src/lib/kinematic.ts
+++ b/app/src/lib/kinematic.ts
@@ -1,32 +1,32 @@
export interface body_state_t {
- omega: number
- phi: number
- psi: number
- xm: number
- ym: number
- zm: number
- feet: number[][]
+ omega: number
+ phi: number
+ psi: number
+ xm: number
+ ym: number
+ zm: number
+ feet: number[][]
}
export interface position {
- x: number
- y: number
- z: number
+ x: number
+ y: number
+ z: number
}
export interface target_position {
- x: number
- z: number
- yaw: number
+ x: number
+ z: number
+ yaw: number
}
export interface KinematicParams {
- coxa: number
- coxa_offset: number
- femur: number
- tibia: number
- L: number
- W: number
+ coxa: number
+ coxa_offset: number
+ femur: number
+ tibia: number
+ L: number
+ W: number
}
const { cos, sin, atan2, acos, sqrt, max, min } = Math
@@ -34,107 +34,114 @@ const { cos, sin, atan2, acos, sqrt, max, min } = Math
const DEG2RAD = 0.017453292519943
export default class Kinematic {
- coxa: number
- coxa_offset: number
- femur: number
- tibia: number
+ coxa: number
+ coxa_offset: number
+ femur: number
+ tibia: number
- L: number
- W: number
+ L: number
+ W: number
- DEG2RAD = DEG2RAD
+ DEG2RAD = DEG2RAD
- mountOffsets: number[][]
+ mountOffsets: number[][]
- invMountRot = [
- [0, 0, -1],
- [0, 1, 0],
- [1, 0, 0]
- ]
-
- constructor(params: KinematicParams) {
- this.coxa = params.coxa
- this.coxa_offset = params.coxa_offset
- this.femur = params.femur
- this.tibia = params.tibia
- this.L = params.L
- this.W = params.W
-
- this.mountOffsets = [
- [this.L / 2, 0, this.W / 2],
- [this.L / 2, 0, -this.W / 2],
- [-this.L / 2, 0, this.W / 2],
- [-this.L / 2, 0, -this.W / 2]
+ invMountRot = [
+ [0, 0, -1],
+ [0, 1, 0],
+ [1, 0, 0]
]
- }
- getDefaultFeetPos(): number[][] {
- return this.mountOffsets.map((offset, i) => {
- return [offset[0], -1, offset[2] + (i % 2 === 1 ? -this.coxa : this.coxa)]
- })
- }
+ constructor(params: KinematicParams) {
+ this.coxa = params.coxa
+ this.coxa_offset = params.coxa_offset
+ this.femur = params.femur
+ this.tibia = params.tibia
+ this.L = params.L
+ this.W = params.W
- calcIK(p: body_state_t): number[] {
- const roll = p.omega * this.DEG2RAD
- const pitch = p.phi * this.DEG2RAD
- const yaw = p.psi * this.DEG2RAD
- const rot = this.euler2R(roll, pitch, yaw)
- const inv_rot = [
- [rot[0][0], rot[1][0], rot[2][0]],
- [rot[0][1], rot[1][1], rot[2][1]],
- [rot[0][2], rot[1][2], rot[2][2]]
- ]
- const inv_trans = [
- -inv_rot[0][0] * p.xm - inv_rot[0][1] * p.ym - inv_rot[0][2] * p.zm,
- -inv_rot[1][0] * p.xm - inv_rot[1][1] * p.ym - inv_rot[1][2] * p.zm,
- -inv_rot[2][0] * p.xm - inv_rot[2][1] * p.ym - inv_rot[2][2] * p.zm
- ]
- return p.feet.flatMap((foot, i) => {
- const [wx, wy, wz] = foot
- const bx = inv_rot[0][0] * wx + inv_rot[0][1] * wy + inv_rot[0][2] * wz + inv_trans[0]
- const by = inv_rot[1][0] * wx + inv_rot[1][1] * wy + inv_rot[1][2] * wz + inv_trans[1]
- const bz = inv_rot[2][0] * wx + inv_rot[2][1] * wy + inv_rot[2][2] * wz + inv_trans[2]
+ this.mountOffsets = [
+ [this.L / 2, 0, this.W / 2],
+ [this.L / 2, 0, -this.W / 2],
+ [-this.L / 2, 0, this.W / 2],
+ [-this.L / 2, 0, -this.W / 2]
+ ]
+ }
- const [mx, my, mz] = this.mountOffsets[i]
- const px = bx - mx,
- py = by - my,
- pz = bz - mz
+ getDefaultFeetPos(): number[][] {
+ return this.mountOffsets.map((offset, i) => {
+ return [offset[0], -1, offset[2] + (i % 2 === 1 ? -this.coxa : this.coxa)]
+ })
+ }
- const lx =
- this.invMountRot[0][0] * px + this.invMountRot[0][1] * py + this.invMountRot[0][2] * pz
- const ly =
- this.invMountRot[1][0] * px + this.invMountRot[1][1] * py + this.invMountRot[1][2] * pz
- const lz =
- this.invMountRot[2][0] * px + this.invMountRot[2][1] * py + this.invMountRot[2][2] * pz
+ calcIK(p: body_state_t): number[] {
+ const roll = p.omega * this.DEG2RAD
+ const pitch = p.phi * this.DEG2RAD
+ const yaw = p.psi * this.DEG2RAD
+ const rot = this.euler2R(roll, pitch, yaw)
+ const inv_rot = [
+ [rot[0][0], rot[1][0], rot[2][0]],
+ [rot[0][1], rot[1][1], rot[2][1]],
+ [rot[0][2], rot[1][2], rot[2][2]]
+ ]
+ const inv_trans = [
+ -inv_rot[0][0] * p.xm - inv_rot[0][1] * p.ym - inv_rot[0][2] * p.zm,
+ -inv_rot[1][0] * p.xm - inv_rot[1][1] * p.ym - inv_rot[1][2] * p.zm,
+ -inv_rot[2][0] * p.xm - inv_rot[2][1] * p.ym - inv_rot[2][2] * p.zm
+ ]
+ return p.feet.flatMap((foot, i) => {
+ const [wx, wy, wz] = foot
+ const bx = inv_rot[0][0] * wx + inv_rot[0][1] * wy + inv_rot[0][2] * wz + inv_trans[0]
+ const by = inv_rot[1][0] * wx + inv_rot[1][1] * wy + inv_rot[1][2] * wz + inv_trans[1]
+ const bz = inv_rot[2][0] * wx + inv_rot[2][1] * wy + inv_rot[2][2] * wz + inv_trans[2]
- const xLocal = i % 2 === 1 ? -lx : lx
- return this.legIK(xLocal, ly, lz)
- })
- }
+ const [mx, my, mz] = this.mountOffsets[i]
+ const px = bx - mx,
+ py = by - my,
+ pz = bz - mz
- private legIK(x: number, y: number, z: number): [number, number, number] {
- const F = sqrt(max(0, x * x + y * y - this.coxa * this.coxa))
- const G = F - this.coxa_offset
- const H = sqrt(G * G + z * z)
- const t1 = -atan2(y, x) - atan2(F, -this.coxa)
- const D =
- (H * H - this.femur * this.femur - this.tibia * this.tibia) / (2 * this.femur * this.tibia)
- const t3 = acos(max(-1, min(1, D)))
- const t2 = atan2(z, G) - atan2(this.tibia * sin(t3), this.femur + this.tibia * cos(t3))
- return [t1, t2, t3]
- }
+ const lx =
+ this.invMountRot[0][0] * px +
+ this.invMountRot[0][1] * py +
+ this.invMountRot[0][2] * pz
+ const ly =
+ this.invMountRot[1][0] * px +
+ this.invMountRot[1][1] * py +
+ this.invMountRot[1][2] * pz
+ const lz =
+ this.invMountRot[2][0] * px +
+ this.invMountRot[2][1] * py +
+ this.invMountRot[2][2] * pz
- private euler2R(roll: number, pitch: number, yaw: number): number[][] {
- const cr = cos(roll),
- sr = sin(roll)
- const cp = cos(pitch),
- sp = sin(pitch)
- const cy = cos(yaw),
- sy = sin(yaw)
- return [
- [cp * cy, -cp * sy, sp],
- [sr * sp * cy + sy * cr, -sr * sp * sy + cr * cy, -sr * cp],
- [sr * sy - sp * cr * cy, sr * cy + sp * sy * cr, cr * cp]
- ]
- }
+ const xLocal = i % 2 === 1 ? -lx : lx
+ return this.legIK(xLocal, ly, lz)
+ })
+ }
+
+ private legIK(x: number, y: number, z: number): [number, number, number] {
+ const F = sqrt(max(0, x * x + y * y - this.coxa * this.coxa))
+ const G = F - this.coxa_offset
+ const H = sqrt(G * G + z * z)
+ const t1 = -atan2(y, x) - atan2(F, -this.coxa)
+ const D =
+ (H * H - this.femur * this.femur - this.tibia * this.tibia) /
+ (2 * this.femur * this.tibia)
+ const t3 = acos(max(-1, min(1, D)))
+ const t2 = atan2(z, G) - atan2(this.tibia * sin(t3), this.femur + this.tibia * cos(t3))
+ return [t1, t2, t3]
+ }
+
+ private euler2R(roll: number, pitch: number, yaw: number): number[][] {
+ const cr = cos(roll),
+ sr = sin(roll)
+ const cp = cos(pitch),
+ sp = sin(pitch)
+ const cy = cos(yaw),
+ sy = sin(yaw)
+ return [
+ [cp * cy, -cp * sy, sp],
+ [sr * sp * cy + sy * cr, -sr * sp * sy + cr * cy, -sr * cp],
+ [sr * sy - sp * cr * cy, sr * cy + sp * sy * cr, cr * cp]
+ ]
+ }
}
diff --git a/app/src/lib/sceneBuilder.ts b/app/src/lib/sceneBuilder.ts
index 43648b2..2147a68 100644
--- a/app/src/lib/sceneBuilder.ts
+++ b/app/src/lib/sceneBuilder.ts
@@ -1,26 +1,26 @@
import {
- Mesh,
- PerspectiveCamera,
- PlaneGeometry,
- Scene,
- WebGLRenderer,
- AmbientLight,
- DirectionalLight,
- PCFSoftShadowMap,
- type GridHelper,
- ArrowHelper,
- Vector3,
- FogExp2,
- CanvasTexture,
- type ColorRepresentation,
- type WebGLRendererParameters,
- MeshPhongMaterial,
- EquirectangularReflectionMapping,
- ACESFilmicToneMapping,
- MathUtils,
- Group,
- MeshBasicMaterial,
- RepeatWrapping
+ Mesh,
+ PerspectiveCamera,
+ PlaneGeometry,
+ Scene,
+ WebGLRenderer,
+ AmbientLight,
+ DirectionalLight,
+ PCFSoftShadowMap,
+ type GridHelper,
+ ArrowHelper,
+ Vector3,
+ FogExp2,
+ CanvasTexture,
+ type ColorRepresentation,
+ type WebGLRendererParameters,
+ MeshPhongMaterial,
+ EquirectangularReflectionMapping,
+ ACESFilmicToneMapping,
+ MathUtils,
+ Group,
+ MeshBasicMaterial,
+ RepeatWrapping
} from 'three'
import { Sky } from 'three/addons/objects/Sky.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
@@ -33,348 +33,348 @@ import { sunCalculator } from './utilities/position-utilities'
export const addScene = () => new Scene()
interface position {
- x?: number
- y?: number
- z?: number
+ x?: number
+ y?: number
+ z?: number
}
interface light {
- color?: ColorRepresentation
- intensity?: number
+ color?: ColorRepresentation
+ intensity?: number
}
interface arrowOptions {
- origin: position
- direction: position
- length?: number
- color?: ColorRepresentation
+ origin: position
+ direction: position
+ length?: number
+ color?: ColorRepresentation
}
type directionalLight = position & light
export default class SceneBuilder {
- public scene: Scene
- public camera!: PerspectiveCamera
- public ground!: Mesh
- public renderer!: WebGLRenderer
- public orbit: OrbitControls
- public callback: Function | undefined
- public gridHelper!: GridHelper
- public model!: URDFRobot
- public liveStreamTexture!: CanvasTexture
- private fog!: FogExp2
- private isLoaded: boolean = false
- public isDragging: boolean = false
- highlightMaterial: any
- sky!: Sky
- transformControl: TransformControls
- public modelGroup!: Group
+ public scene: Scene
+ public camera!: PerspectiveCamera
+ public ground!: Mesh
+ public renderer!: WebGLRenderer
+ public orbit: OrbitControls
+ public callback: Function | undefined
+ public gridHelper!: GridHelper
+ public model!: URDFRobot
+ public liveStreamTexture!: CanvasTexture
+ private fog!: FogExp2
+ private isLoaded: boolean = false
+ public isDragging: boolean = false
+ highlightMaterial: any
+ sky!: Sky
+ transformControl: TransformControls
+ public modelGroup!: Group
- constructor() {
- this.scene = new Scene()
- if (this.scene.environment?.mapping) {
- this.scene.environment.mapping = EquirectangularReflectionMapping
- }
- return this
- }
-
- public addRenderer = (parameters?: WebGLRendererParameters) => {
- this.renderer = new WebGLRenderer(parameters)
- this.renderer.outputColorSpace = 'srgb'
- this.renderer.shadowMap.enabled = true
- this.renderer.shadowMap.type = PCFSoftShadowMap
- this.renderer.toneMapping = ACESFilmicToneMapping
- this.renderer.toneMappingExposure = 0.85
- if (!parameters?.canvas) document.body.appendChild(this.renderer.domElement)
- return this
- }
-
- public addSky = () => {
- this.sky = new Sky()
- this.sky.scale.setScalar(450000)
- this.scene.add(this.sky)
- const effectController = {
- turbidity: 10,
- rayleigh: 3,
- mieCoefficient: 0.005,
- mieDirectionalG: 0.7,
- elevation: sunCalculator.calculateSunElevation(),
- azimuth: 200,
- exposure: this.renderer.toneMappingExposure
- }
- const uniforms = this.sky.material.uniforms
- uniforms['turbidity'].value = effectController.turbidity
- uniforms['rayleigh'].value = effectController.rayleigh
- uniforms['mieCoefficient'].value = effectController.mieCoefficient
- uniforms['mieDirectionalG'].value = effectController.mieDirectionalG
- this.renderer.toneMappingExposure = 0.5
- const phi = MathUtils.degToRad(90 - effectController.elevation)
- const theta = MathUtils.degToRad(effectController.azimuth)
- const sun = new Vector3()
-
- sun.setFromSphericalCoords(1, phi, theta)
- uniforms['sunPosition'].value.copy(sun)
- return this
- }
-
- public addPerspectiveCamera = (options: position) => {
- this.camera = new PerspectiveCamera()
- this.camera.position.set(options.x ?? 0, options.y ?? 2.7, options.z ?? 0)
- this.scene.add(this.camera)
- return this
- }
-
- public addGroundPlane = (options?: position) => {
- const checkerboardTexture = this.createCheckerboardTexture(1024, 2)
- checkerboardTexture.wrapS = RepeatWrapping
- checkerboardTexture.wrapT = RepeatWrapping
- checkerboardTexture.repeat.set(100, 100)
- const checkerboardMat = new MeshBasicMaterial({
- map: checkerboardTexture,
- opacity: 0.1,
- transparent: true
- })
-
- const plane = new PlaneGeometry(400, 400)
-
- this.ground = new Mesh(plane, checkerboardMat)
- this.ground.rotation.x = -Math.PI / 2
- this.ground.position.set(options?.x ?? 0, options?.y ?? 0.01, options?.z ?? 0)
- this.ground.receiveShadow = true
- this.scene.add(this.ground)
-
- const mirror = new Reflector(plane, {
- clipBias: 0.003,
- textureWidth: window.innerWidth * window.devicePixelRatio,
- textureHeight: window.innerHeight * window.devicePixelRatio,
- color: 0x00bfff
- })
- mirror.rotateX(-Math.PI / 2)
- this.scene.add(mirror)
-
- return this
- }
-
- public addOrbitControls = (minDistance: number, maxDistance: number, autoRotate = true) => {
- this.orbit = new OrbitControls(this.camera, this.renderer.domElement)
- this.orbit.minDistance = minDistance + (maxDistance - minDistance) / 2
- this.orbit.maxDistance = maxDistance
- this.orbit.autoRotate = autoRotate
- this.orbit.update()
- this.orbit.minDistance = minDistance
- return this
- }
-
- public addAmbientLight = (options: light) => {
- const ambientLight = new AmbientLight(options.color, options.intensity)
- this.scene.add(ambientLight)
- return this
- }
-
- public addDirectionalLight = (options: directionalLight) => {
- const directionalLight = new DirectionalLight(options.color, options.intensity)
- directionalLight.castShadow = true
- directionalLight.shadow.camera.top = 10
- directionalLight.shadow.camera.bottom = -10
- directionalLight.shadow.camera.right = 10
- directionalLight.shadow.camera.left = -10
- directionalLight.shadow.mapSize.set(4096, 4096)
-
- directionalLight.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0)
- this.scene.add(directionalLight)
- return this
- }
-
- private createCheckerboardTexture = (size: number, squares: number) => {
- const canvas = document.createElement('canvas')
- canvas.width = size
- canvas.height = size
- const context = canvas.getContext('2d')
-
- const squareSize = size / squares
-
- for (let y = 0; y < squares; y++) {
- for (let x = 0; x < squares; x++) {
- context!.fillStyle = (x + y) % 2 === 0 ? '#ffffff' : '#000000'
- context!.fillRect(x * squareSize, y * squareSize, squareSize, squareSize)
- }
- }
-
- const texture = new CanvasTexture(canvas)
- texture.wrapS = texture.wrapT = RepeatWrapping
- texture.anisotropy = 16
- return texture
- }
-
- public addFogExp2 = (color: ColorRepresentation, density?: number) => {
- this.scene.fog = new FogExp2(color, density)
- return this
- }
-
- public fillParent = () => {
- const parentElement = this.renderer.domElement.parentElement
- if (parentElement) {
- const width = parentElement.clientWidth
- const height = parentElement.clientHeight
- this.handleResize(width, height)
- }
- return this
- }
-
- public handleResize = (width = window.innerWidth, height = window.innerHeight) => {
- this.renderer.setSize(width, height)
- this.renderer.setPixelRatio(window.devicePixelRatio)
- this.camera.aspect = width / height
- this.camera.updateProjectionMatrix()
- return this
- }
-
- public addRenderCb = (callback: Function) => {
- this.callback = callback
- return this
- }
-
- public startRenderLoop = () => {
- this.renderer.setAnimationLoop(() => {
- this.renderer.render(this.scene, this.camera)
- this.orbit.update()
- this.handleRobotShadow()
- if (this.callback) this.callback()
- if (!this.liveStreamTexture) return
- })
- return this
- }
-
- public addArrowHelper = (options?: arrowOptions) => {
- const dir = new Vector3(
- options?.direction.x ?? 0,
- options?.direction.y ?? 0,
- options?.direction.z ?? 0
- )
- const origin = new Vector3(
- options?.origin.x ?? 0,
- options?.origin.y ?? 0,
- options?.origin.z ?? 0
- )
- const arrowHelper = new ArrowHelper(
- dir,
- origin,
- options?.length ?? 1.5,
- options?.color ?? 0xff0000
- )
- this.scene.add(arrowHelper)
- return this
- }
-
- private setJointValue(jointName: string, angle: number) {
- if (!this.model) return
- if (!this.model.joints[jointName]) return
- this.model.joints[jointName].setJointValue(angle)
- }
-
- isJoint = (j: URDFJoint) => j.isURDFJoint && j.jointType !== 'fixed'
-
- highlightLinkGeometry = (m: URDFMimicJoint, revert: boolean, material: MeshPhongMaterial) => {
- const traverse = (c: any) => {
- if (c.type === 'Mesh') {
- if (revert) {
- c.material = c.__origMaterial
- delete c.__origMaterial
- } else {
- c.__origMaterial = c.material
- c.material = material
+ constructor() {
+ this.scene = new Scene()
+ if (this.scene.environment?.mapping) {
+ this.scene.environment.mapping = EquirectangularReflectionMapping
}
- }
+ return this
+ }
- if (c === m || !this.isJoint(c)) {
- for (let i = 0; i < c.children.length; i++) {
- const child = c.children[i]
- if (!child.isURDFCollider) {
- traverse(c.children[i])
- }
+ public addRenderer = (parameters?: WebGLRendererParameters) => {
+ this.renderer = new WebGLRenderer(parameters)
+ this.renderer.outputColorSpace = 'srgb'
+ this.renderer.shadowMap.enabled = true
+ this.renderer.shadowMap.type = PCFSoftShadowMap
+ this.renderer.toneMapping = ACESFilmicToneMapping
+ this.renderer.toneMappingExposure = 0.85
+ if (!parameters?.canvas) document.body.appendChild(this.renderer.domElement)
+ return this
+ }
+
+ public addSky = () => {
+ this.sky = new Sky()
+ this.sky.scale.setScalar(450000)
+ this.scene.add(this.sky)
+ const effectController = {
+ turbidity: 10,
+ rayleigh: 3,
+ mieCoefficient: 0.005,
+ mieDirectionalG: 0.7,
+ elevation: sunCalculator.calculateSunElevation(),
+ azimuth: 200,
+ exposure: this.renderer.toneMappingExposure
}
- }
+ const uniforms = this.sky.material.uniforms
+ uniforms['turbidity'].value = effectController.turbidity
+ uniforms['rayleigh'].value = effectController.rayleigh
+ uniforms['mieCoefficient'].value = effectController.mieCoefficient
+ uniforms['mieDirectionalG'].value = effectController.mieDirectionalG
+ this.renderer.toneMappingExposure = 0.5
+ const phi = MathUtils.degToRad(90 - effectController.elevation)
+ const theta = MathUtils.degToRad(effectController.azimuth)
+ const sun = new Vector3()
+
+ sun.setFromSphericalCoords(1, phi, theta)
+ uniforms['sunPosition'].value.copy(sun)
+ return this
}
- traverse(m)
- }
- public addTransformControls = (model: any) => {
- this.transformControl = new TransformControls(this.camera, this.renderer.domElement)
- this.transformControl.addEventListener('dragging-changed', (event: any) => {
- this.orbit.enabled = !event.value
- this.isDragging = !event.value
- })
- this.transformControl.attach(model)
- this.scene.add(this.transformControl)
- this.transformControl.setMode('rotate')
- return this
- }
-
- public addModel = (model: any) => {
- this.modelGroup = new Group()
- this.modelGroup.add(model)
- this.model = model
- this.scene.add(this.modelGroup)
- return this
- }
-
- public addDragControl = (updateAngle: any) => {
- const highlightColor = '#FFFFFF'
- const highlightMaterial = new MeshPhongMaterial({
- shininess: 10,
- color: highlightColor,
- emissive: highlightColor,
- emissiveIntensity: 0.9
- })
-
- const dragControls = new PointerURDFDragControls(
- this.scene,
- this.camera,
- this.renderer.domElement
- )
- dragControls.updateJoint = (joint: URDFMimicJoint, angle: number) => {
- this.setJointValue(joint.name, angle)
- updateAngle(joint.name, angle)
+ public addPerspectiveCamera = (options: position) => {
+ this.camera = new PerspectiveCamera()
+ this.camera.position.set(options.x ?? 0, options.y ?? 2.7, options.z ?? 0)
+ this.scene.add(this.camera)
+ return this
}
- dragControls.onDragStart = () => {
- this.orbit.enabled = false
- this.isDragging = true
+
+ public addGroundPlane = (options?: position) => {
+ const checkerboardTexture = this.createCheckerboardTexture(1024, 2)
+ checkerboardTexture.wrapS = RepeatWrapping
+ checkerboardTexture.wrapT = RepeatWrapping
+ checkerboardTexture.repeat.set(100, 100)
+ const checkerboardMat = new MeshBasicMaterial({
+ map: checkerboardTexture,
+ opacity: 0.1,
+ transparent: true
+ })
+
+ const plane = new PlaneGeometry(400, 400)
+
+ this.ground = new Mesh(plane, checkerboardMat)
+ this.ground.rotation.x = -Math.PI / 2
+ this.ground.position.set(options?.x ?? 0, options?.y ?? 0.01, options?.z ?? 0)
+ this.ground.receiveShadow = true
+ this.scene.add(this.ground)
+
+ const mirror = new Reflector(plane, {
+ clipBias: 0.003,
+ textureWidth: window.innerWidth * window.devicePixelRatio,
+ textureHeight: window.innerHeight * window.devicePixelRatio,
+ color: 0x00bfff
+ })
+ mirror.rotateX(-Math.PI / 2)
+ this.scene.add(mirror)
+
+ return this
}
- dragControls.onDragEnd = () => {
- this.orbit.enabled = true
- this.isDragging = false
+
+ public addOrbitControls = (minDistance: number, maxDistance: number, autoRotate = true) => {
+ this.orbit = new OrbitControls(this.camera, this.renderer.domElement)
+ this.orbit.minDistance = minDistance + (maxDistance - minDistance) / 2
+ this.orbit.maxDistance = maxDistance
+ this.orbit.autoRotate = autoRotate
+ this.orbit.update()
+ this.orbit.minDistance = minDistance
+ return this
}
- dragControls.onHover = (joint: URDFMimicJoint) =>
- this.highlightLinkGeometry(joint, false, highlightMaterial)
- dragControls.onUnhover = (joint: URDFMimicJoint) =>
- this.highlightLinkGeometry(joint, true, highlightMaterial)
- this.renderer.domElement.addEventListener(
- 'touchstart',
- data => dragControls._mouseDown(data.touches[0]),
- { passive: true }
- )
- this.renderer.domElement.addEventListener(
- 'touchmove',
- data => dragControls._mouseMove(data.touches[0]),
- { passive: true }
- )
- this.renderer.domElement.addEventListener(
- 'touchend',
- data => dragControls._mouseUp(data.touches[0]),
- { passive: true }
- )
- return this
- }
+ public addAmbientLight = (options: light) => {
+ const ambientLight = new AmbientLight(options.color, options.intensity)
+ this.scene.add(ambientLight)
+ return this
+ }
- public toggleFog = () => {
- this.scene.fog = this.scene.fog ? null : this.fog
- }
+ public addDirectionalLight = (options: directionalLight) => {
+ const directionalLight = new DirectionalLight(options.color, options.intensity)
+ directionalLight.castShadow = true
+ directionalLight.shadow.camera.top = 10
+ directionalLight.shadow.camera.bottom = -10
+ directionalLight.shadow.camera.right = 10
+ directionalLight.shadow.camera.left = -10
+ directionalLight.shadow.mapSize.set(4096, 4096)
- private handleRobotShadow = () => {
- if (this.isLoaded) return
- const intervalId = setInterval(() => this.model?.traverse(c => (c.castShadow = true)), 10)
- setTimeout(() => clearInterval(intervalId), 1000)
- this.isLoaded = true
- }
+ directionalLight.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0)
+ this.scene.add(directionalLight)
+ return this
+ }
+
+ private createCheckerboardTexture = (size: number, squares: number) => {
+ const canvas = document.createElement('canvas')
+ canvas.width = size
+ canvas.height = size
+ const context = canvas.getContext('2d')
+
+ const squareSize = size / squares
+
+ for (let y = 0; y < squares; y++) {
+ for (let x = 0; x < squares; x++) {
+ context!.fillStyle = (x + y) % 2 === 0 ? '#ffffff' : '#000000'
+ context!.fillRect(x * squareSize, y * squareSize, squareSize, squareSize)
+ }
+ }
+
+ const texture = new CanvasTexture(canvas)
+ texture.wrapS = texture.wrapT = RepeatWrapping
+ texture.anisotropy = 16
+ return texture
+ }
+
+ public addFogExp2 = (color: ColorRepresentation, density?: number) => {
+ this.scene.fog = new FogExp2(color, density)
+ return this
+ }
+
+ public fillParent = () => {
+ const parentElement = this.renderer.domElement.parentElement
+ if (parentElement) {
+ const width = parentElement.clientWidth
+ const height = parentElement.clientHeight
+ this.handleResize(width, height)
+ }
+ return this
+ }
+
+ public handleResize = (width = window.innerWidth, height = window.innerHeight) => {
+ this.renderer.setSize(width, height)
+ this.renderer.setPixelRatio(window.devicePixelRatio)
+ this.camera.aspect = width / height
+ this.camera.updateProjectionMatrix()
+ return this
+ }
+
+ public addRenderCb = (callback: Function) => {
+ this.callback = callback
+ return this
+ }
+
+ public startRenderLoop = () => {
+ this.renderer.setAnimationLoop(() => {
+ this.renderer.render(this.scene, this.camera)
+ this.orbit.update()
+ this.handleRobotShadow()
+ if (this.callback) this.callback()
+ if (!this.liveStreamTexture) return
+ })
+ return this
+ }
+
+ public addArrowHelper = (options?: arrowOptions) => {
+ const dir = new Vector3(
+ options?.direction.x ?? 0,
+ options?.direction.y ?? 0,
+ options?.direction.z ?? 0
+ )
+ const origin = new Vector3(
+ options?.origin.x ?? 0,
+ options?.origin.y ?? 0,
+ options?.origin.z ?? 0
+ )
+ const arrowHelper = new ArrowHelper(
+ dir,
+ origin,
+ options?.length ?? 1.5,
+ options?.color ?? 0xff0000
+ )
+ this.scene.add(arrowHelper)
+ return this
+ }
+
+ private setJointValue(jointName: string, angle: number) {
+ if (!this.model) return
+ if (!this.model.joints[jointName]) return
+ this.model.joints[jointName].setJointValue(angle)
+ }
+
+ isJoint = (j: URDFJoint) => j.isURDFJoint && j.jointType !== 'fixed'
+
+ highlightLinkGeometry = (m: URDFMimicJoint, revert: boolean, material: MeshPhongMaterial) => {
+ const traverse = (c: any) => {
+ if (c.type === 'Mesh') {
+ if (revert) {
+ c.material = c.__origMaterial
+ delete c.__origMaterial
+ } else {
+ c.__origMaterial = c.material
+ c.material = material
+ }
+ }
+
+ if (c === m || !this.isJoint(c)) {
+ for (let i = 0; i < c.children.length; i++) {
+ const child = c.children[i]
+ if (!child.isURDFCollider) {
+ traverse(c.children[i])
+ }
+ }
+ }
+ }
+ traverse(m)
+ }
+
+ public addTransformControls = (model: any) => {
+ this.transformControl = new TransformControls(this.camera, this.renderer.domElement)
+ this.transformControl.addEventListener('dragging-changed', (event: any) => {
+ this.orbit.enabled = !event.value
+ this.isDragging = !event.value
+ })
+ this.transformControl.attach(model)
+ this.scene.add(this.transformControl)
+ this.transformControl.setMode('rotate')
+ return this
+ }
+
+ public addModel = (model: any) => {
+ this.modelGroup = new Group()
+ this.modelGroup.add(model)
+ this.model = model
+ this.scene.add(this.modelGroup)
+ return this
+ }
+
+ public addDragControl = (updateAngle: any) => {
+ const highlightColor = '#FFFFFF'
+ const highlightMaterial = new MeshPhongMaterial({
+ shininess: 10,
+ color: highlightColor,
+ emissive: highlightColor,
+ emissiveIntensity: 0.9
+ })
+
+ const dragControls = new PointerURDFDragControls(
+ this.scene,
+ this.camera,
+ this.renderer.domElement
+ )
+ dragControls.updateJoint = (joint: URDFMimicJoint, angle: number) => {
+ this.setJointValue(joint.name, angle)
+ updateAngle(joint.name, angle)
+ }
+ dragControls.onDragStart = () => {
+ this.orbit.enabled = false
+ this.isDragging = true
+ }
+ dragControls.onDragEnd = () => {
+ this.orbit.enabled = true
+ this.isDragging = false
+ }
+ dragControls.onHover = (joint: URDFMimicJoint) =>
+ this.highlightLinkGeometry(joint, false, highlightMaterial)
+ dragControls.onUnhover = (joint: URDFMimicJoint) =>
+ this.highlightLinkGeometry(joint, true, highlightMaterial)
+
+ this.renderer.domElement.addEventListener(
+ 'touchstart',
+ data => dragControls._mouseDown(data.touches[0]),
+ { passive: true }
+ )
+ this.renderer.domElement.addEventListener(
+ 'touchmove',
+ data => dragControls._mouseMove(data.touches[0]),
+ { passive: true }
+ )
+ this.renderer.domElement.addEventListener(
+ 'touchend',
+ data => dragControls._mouseUp(data.touches[0]),
+ { passive: true }
+ )
+ return this
+ }
+
+ public toggleFog = () => {
+ this.scene.fog = this.scene.fog ? null : this.fog
+ }
+
+ private handleRobotShadow = () => {
+ if (this.isLoaded) return
+ const intervalId = setInterval(() => this.model?.traverse(c => (c.castShadow = true)), 10)
+ setTimeout(() => clearInterval(intervalId), 1000)
+ this.isLoaded = true
+ }
}
diff --git a/app/src/lib/services/file-service.ts b/app/src/lib/services/file-service.ts
index 70f27b5..ef9d668 100644
--- a/app/src/lib/services/file-service.ts
+++ b/app/src/lib/services/file-service.ts
@@ -1,54 +1,53 @@
-import { Result } from '$lib/utilities/result';
-import { browser } from '$app/environment';
+import { Result } from '$lib/utilities/result'
+import { browser } from '$app/environment'
class FileService {
- private dbPromise: Promise> | null = browser
- ? this.openDatabase()
- : null;
+ private dbPromise: Promise> | null =
+ browser ? this.openDatabase() : null
- private async openDatabase(): Promise> {
- return new Promise((resolve) => {
- const request = indexedDB.open('fileStorageDB', 1);
+ private async openDatabase(): Promise> {
+ return new Promise(resolve => {
+ const request = indexedDB.open('fileStorageDB', 1)
- request.onupgradeneeded = () => {
- request.result.createObjectStore('files');
- };
- request.onsuccess = () => resolve(Result.ok(request.result));
- request.onerror = () => resolve(Result.err('Error opening database'));
- });
- }
+ request.onupgradeneeded = () => {
+ request.result.createObjectStore('files')
+ }
+ request.onsuccess = () => resolve(Result.ok(request.result))
+ request.onerror = () => resolve(Result.err('Error opening database'))
+ })
+ }
- private async getStore(mode: IDBTransactionMode): Promise> {
- if (!browser || !this.dbPromise)
- return Result.err('Not running in browser or DB not initialized');
- const dbResult = await this.dbPromise;
- if (dbResult.isErr()) return Result.err('Database not initialized');
- const store = dbResult.inner.transaction('files', mode).objectStore('files');
- return Result.ok(store);
- }
+ private async getStore(mode: IDBTransactionMode): Promise> {
+ if (!browser || !this.dbPromise)
+ return Result.err('Not running in browser or DB not initialized')
+ const dbResult = await this.dbPromise
+ if (dbResult.isErr()) return Result.err('Database not initialized')
+ const store = dbResult.inner.transaction('files', mode).objectStore('files')
+ return Result.ok(store)
+ }
- public async saveFile(key: string, file: Uint8Array): Promise> {
- const storeResult = await this.getStore('readwrite');
- if (storeResult.isErr()) return Result.err('Failed to access store');
+ public async saveFile(key: string, file: Uint8Array): Promise> {
+ const storeResult = await this.getStore('readwrite')
+ if (storeResult.isErr()) return Result.err('Failed to access store')
- return new Promise((resolve) => {
- const request = storeResult.inner.put(file, key);
- request.onsuccess = () => resolve(Result.ok(request.result));
- request.onerror = () => resolve(Result.err('Failed to save file'));
- });
- }
+ return new Promise(resolve => {
+ const request = storeResult.inner.put(file, key)
+ request.onsuccess = () => resolve(Result.ok(request.result))
+ request.onerror = () => resolve(Result.err('Failed to save file'))
+ })
+ }
- public async getFile(key: string): Promise> {
- const storeResult = await this.getStore('readonly');
- if (storeResult.isErr()) return Result.err('Failed to access store');
+ public async getFile(key: string): Promise> {
+ const storeResult = await this.getStore('readonly')
+ if (storeResult.isErr()) return Result.err('Failed to access store')
- return new Promise((resolve) => {
- const request = storeResult.inner.get(key);
- request.onsuccess = () =>
- resolve(request.result ? Result.ok(request.result) : Result.err('File not found'));
- request.onerror = () => resolve(Result.err('Failed to retrieve file'));
- });
- }
+ return new Promise(resolve => {
+ const request = storeResult.inner.get(key)
+ request.onsuccess = () =>
+ resolve(request.result ? Result.ok(request.result) : Result.err('File not found'))
+ request.onerror = () => resolve(Result.err('Failed to retrieve file'))
+ })
+ }
}
-export default browser ? new FileService() : null;
\ No newline at end of file
+export default browser ? new FileService() : null
diff --git a/app/src/lib/services/index.ts b/app/src/lib/services/index.ts
index 7a112d0..4a56b34 100644
--- a/app/src/lib/services/index.ts
+++ b/app/src/lib/services/index.ts
@@ -1,2 +1,2 @@
-export { default as fileService } from './file-service';
-export { default as resultService } from './result-service';
+export { default as fileService } from './file-service'
+export { default as resultService } from './result-service'
diff --git a/app/src/lib/services/result-service.ts b/app/src/lib/services/result-service.ts
index ceb4900..c92b3bb 100644
--- a/app/src/lib/services/result-service.ts
+++ b/app/src/lib/services/result-service.ts
@@ -1,19 +1,19 @@
-import { errorLogs, latestErrorLog } from '$lib/stores';
-import type { Result } from '$lib/utilities';
+import { errorLogs, latestErrorLog } from '$lib/stores'
+import type { Result } from '$lib/utilities'
class ResultService {
- public handleResult(result: Result, tag?: string) {
- if (result.isErr()) {
- const errorLogEntry = { tag, message: result.inner, exception: result.exception };
- latestErrorLog.set(errorLogEntry);
- errorLogs.update((entries) => {
- entries.push(errorLogEntry);
- return entries;
- });
- }
+ public handleResult(result: Result, tag?: string) {
+ if (result.isErr()) {
+ const errorLogEntry = { tag, message: result.inner, exception: result.exception }
+ latestErrorLog.set(errorLogEntry)
+ errorLogs.update(entries => {
+ entries.push(errorLogEntry)
+ return entries
+ })
+ }
- return result;
- }
+ return result
+ }
}
-export default new ResultService();
+export default new ResultService()
diff --git a/app/src/lib/stores/analytics.ts b/app/src/lib/stores/analytics.ts
index 14c8299..01f44b6 100644
--- a/app/src/lib/stores/analytics.ts
+++ b/app/src/lib/stores/analytics.ts
@@ -1,55 +1,69 @@
-import { type Analytics } from '$lib/types/models';
-import { writable } from 'svelte/store';
+import { type Analytics } from '$lib/types/models'
+import { writable } from 'svelte/store'
let analytics_data = {
- uptime: [],
- free_heap: [],
- total_heap: [],
- used_heap: [],
- min_free_heap: [],
- max_alloc_heap: [],
- fs_used: [],
- fs_total: [],
- core_temp: [],
- cpu0_usage: [],
- cpu1_usage: [],
- cpu_usage: []
-};
-
-const maxAnalyticsData = 100;
-
-function createAnalytics() {
- const { subscribe, update } = writable(analytics_data);
-
- return {
- subscribe,
- addData: (content: Analytics) => {
- update((analytics_data) => ({
- ...analytics_data,
- uptime: [...analytics_data.uptime, content.uptime].slice(-maxAnalyticsData),
- free_heap: [...analytics_data.free_heap, content.free_heap / 1000].slice(-maxAnalyticsData),
- total_heap: [...analytics_data.total_heap, content.total_heap / 1000].slice(
- -maxAnalyticsData
- ),
- used_heap: [
- ...analytics_data.used_heap,
- (content.total_heap - content.free_heap) / 1000
- ].slice(-maxAnalyticsData),
- min_free_heap: [...analytics_data.min_free_heap, content.min_free_heap / 1000].slice(
- -maxAnalyticsData
- ),
- max_alloc_heap: [...analytics_data.max_alloc_heap, content.max_alloc_heap / 1000].slice(
- -maxAnalyticsData
- ),
- fs_used: [...analytics_data.fs_used, content.fs_used / 1000].slice(-maxAnalyticsData),
- fs_total: [...analytics_data.fs_total, content.fs_total / 1000].slice(-maxAnalyticsData),
- core_temp: [...analytics_data.core_temp, content.core_temp].slice(-maxAnalyticsData),
- cpu0_usage: [...analytics_data.cpu0_usage, content.cpu0_usage].slice(-maxAnalyticsData),
- cpu1_usage: [...analytics_data.cpu1_usage, content.cpu1_usage].slice(-maxAnalyticsData),
- cpu_usage: [...analytics_data.cpu_usage, content.cpu_usage].slice(-maxAnalyticsData)
- }));
- }
- };
+ uptime: [],
+ free_heap: [],
+ total_heap: [],
+ used_heap: [],
+ min_free_heap: [],
+ max_alloc_heap: [],
+ fs_used: [],
+ fs_total: [],
+ core_temp: [],
+ cpu0_usage: [],
+ cpu1_usage: [],
+ cpu_usage: []
}
-export const analytics = createAnalytics();
+const maxAnalyticsData = 100
+
+function createAnalytics() {
+ const { subscribe, update } = writable(analytics_data)
+
+ return {
+ subscribe,
+ addData: (content: Analytics) => {
+ update(analytics_data => ({
+ ...analytics_data,
+ uptime: [...analytics_data.uptime, content.uptime].slice(-maxAnalyticsData),
+ free_heap: [...analytics_data.free_heap, content.free_heap / 1000].slice(
+ -maxAnalyticsData
+ ),
+ total_heap: [...analytics_data.total_heap, content.total_heap / 1000].slice(
+ -maxAnalyticsData
+ ),
+ used_heap: [
+ ...analytics_data.used_heap,
+ (content.total_heap - content.free_heap) / 1000
+ ].slice(-maxAnalyticsData),
+ min_free_heap: [
+ ...analytics_data.min_free_heap,
+ content.min_free_heap / 1000
+ ].slice(-maxAnalyticsData),
+ max_alloc_heap: [
+ ...analytics_data.max_alloc_heap,
+ content.max_alloc_heap / 1000
+ ].slice(-maxAnalyticsData),
+ fs_used: [...analytics_data.fs_used, content.fs_used / 1000].slice(
+ -maxAnalyticsData
+ ),
+ fs_total: [...analytics_data.fs_total, content.fs_total / 1000].slice(
+ -maxAnalyticsData
+ ),
+ core_temp: [...analytics_data.core_temp, content.core_temp].slice(
+ -maxAnalyticsData
+ ),
+ cpu0_usage: [...analytics_data.cpu0_usage, content.cpu0_usage].slice(
+ -maxAnalyticsData
+ ),
+ cpu1_usage: [...analytics_data.cpu1_usage, content.cpu1_usage].slice(
+ -maxAnalyticsData
+ ),
+ cpu_usage: [...analytics_data.cpu_usage, content.cpu_usage].slice(-maxAnalyticsData)
+ }))
+ }
+ }
+}
+
+export const analytics = createAnalytics()
diff --git a/app/src/lib/stores/application.ts b/app/src/lib/stores/application.ts
index 4fa96c4..1975f79 100644
--- a/app/src/lib/stores/application.ts
+++ b/app/src/lib/stores/application.ts
@@ -1,67 +1,67 @@
-import { persistentStore } from '$lib/utilities';
-import { get, type Writable } from 'svelte/store';
+import { persistentStore } from '$lib/utilities'
+import { get, type Writable } from 'svelte/store'
-import Visualization from '$lib/components/Visualization.svelte';
-import Stream from '$lib/components/Stream.svelte';
-import ChartWidget from '$lib/components/widget/ChartWidget.svelte';
+import Visualization from '$lib/components/Visualization.svelte'
+import Stream from '$lib/components/Stream.svelte'
+import ChartWidget from '$lib/components/widget/ChartWidget.svelte'
export interface WidgetConfig {
- id: string | number;
- component: keyof typeof WidgetComponents;
- props?: Record;
+ id: string | number
+ component: keyof typeof WidgetComponents
+ props?: Record
}
export interface WidgetContainerConfig {
- id: string | number;
- layout?: 'row' | 'column' | 'wrap';
- header?: string;
- widgets: Array;
+ id: string | number
+ layout?: 'row' | 'column' | 'wrap'
+ header?: string
+ widgets: Array
}
export const isWidgetConfig = (
- widget: WidgetConfig | WidgetContainerConfig
-): widget is WidgetConfig => 'component' in widget;
+ widget: WidgetConfig | WidgetContainerConfig
+): widget is WidgetConfig => 'component' in widget
export const WidgetComponents = {
- Visualization,
- Stream,
- ChartWidget
-};
+ Visualization,
+ Stream,
+ ChartWidget
+}
interface View {
- name: string;
- content: WidgetContainerConfig;
+ name: string
+ content: WidgetContainerConfig
}
const defaultViews: View[] = [
- {
- name: 'Stream',
- content: {
- id: 'root',
- layout: 'column',
- widgets: [{ id: 2, component: 'Stream' }]
- }
- },
- {
- name: '3D representation',
- content: {
- id: 'root',
- layout: 'column',
- widgets: [{ id: 2, component: 'Visualization', props: { debug: true } }]
- }
- },
- {
- name: 'Split screen',
- content: {
- id: 'root',
- widgets: [
- { id: 2, component: 'Stream' },
- { id: 2, component: 'Visualization', props: { debug: true } }
- ]
- }
- }
-];
+ {
+ name: 'Stream',
+ content: {
+ id: 'root',
+ layout: 'column',
+ widgets: [{ id: 2, component: 'Stream' }]
+ }
+ },
+ {
+ name: '3D representation',
+ content: {
+ id: 'root',
+ layout: 'column',
+ widgets: [{ id: 2, component: 'Visualization', props: { debug: true } }]
+ }
+ },
+ {
+ name: 'Split screen',
+ content: {
+ id: 'root',
+ widgets: [
+ { id: 2, component: 'Stream' },
+ { id: 2, component: 'Visualization', props: { debug: true } }
+ ]
+ }
+ }
+]
-export const views: Writable = persistentStore('views', defaultViews);
+export const views: Writable = persistentStore('views', defaultViews)
-export const selectedView = persistentStore('selected_view', get(views)[0].name);
+export const selectedView = persistentStore('selected_view', get(views)[0].name)
diff --git a/app/src/lib/stores/featureFlags.ts b/app/src/lib/stores/featureFlags.ts
index f2177ed..6f978d6 100644
--- a/app/src/lib/stores/featureFlags.ts
+++ b/app/src/lib/stores/featureFlags.ts
@@ -8,55 +8,55 @@ import { base } from '$app/paths'
let featureFlagsStore: Writable>
export function useFeatureFlags() {
- if (!featureFlagsStore) {
- featureFlagsStore = persistentStore>('FeatureFlags', {})
+ if (!featureFlagsStore) {
+ featureFlagsStore = persistentStore>('FeatureFlags', {})
- api.get>('/api/features').then(result => {
- if (result.isOk()) featureFlagsStore.set(result.inner)
- else {
- notifications.error('Feature flag could not be fetched', 2500)
- }
- })
- }
+ api.get>('/api/features').then(result => {
+ if (result.isOk()) featureFlagsStore.set(result.inner)
+ else {
+ notifications.error('Feature flag could not be fetched', 2500)
+ }
+ })
+ }
- return featureFlagsStore
+ return featureFlagsStore
}
export const variants = {
- SPOTMICRO_ESP32: {
- model: `${base}/spot_micro.urdf.xacro`,
- stl: `${base}/stl.zip`,
- kinematics: {
- coxa: 60.5 / 100,
- coxa_offset: 10 / 100,
- femur: 111.7 / 100,
- tibia: 118.5 / 100,
- L: 207.5 / 100,
- W: 78 / 100
+ SPOTMICRO_ESP32: {
+ model: `${base}/spot_micro.urdf.xacro`,
+ stl: `${base}/stl.zip`,
+ kinematics: {
+ coxa: 60.5 / 100,
+ coxa_offset: 10 / 100,
+ femur: 111.7 / 100,
+ tibia: 118.5 / 100,
+ L: 207.5 / 100,
+ W: 78 / 100
+ }
+ },
+ SPOTMICRO_YERTLE: {
+ model: `${base}/yertle.URDF`,
+ stl: `${base}/URDF.zip`,
+ kinematics: {
+ coxa: 35 / 100,
+ coxa_offset: 0 / 100,
+ femur: 130 / 100,
+ tibia: 130 / 100,
+ L: 240 / 100,
+ W: 78 / 100
+ }
}
- },
- SPOTMICRO_YERTLE: {
- model: `${base}/yertle.URDF`,
- stl: `${base}/URDF.zip`,
- kinematics: {
- coxa: 35 / 100,
- coxa_offset: 0 / 100,
- femur: 130 / 100,
- tibia: 130 / 100,
- L: 240 / 100,
- W: 78 / 100
- }
- }
}
export const currentVariant = derived(useFeatureFlags(), $flagStore => {
- const variantFlag = $flagStore['variant'] as string
- return variantFlag && variants[variantFlag as keyof typeof variants] ?
- variants[variantFlag as keyof typeof variants]
- : variants.SPOTMICRO_ESP32
+ const variantFlag = $flagStore['variant'] as string
+ return variantFlag && variants[variantFlag as keyof typeof variants] ?
+ variants[variantFlag as keyof typeof variants]
+ : variants.SPOTMICRO_ESP32
})
export const currentKinematic = derived(
- currentVariant,
- $variant => new Kinematic($variant.kinematics)
+ currentVariant,
+ $variant => new Kinematic($variant.kinematics)
)
diff --git a/app/src/lib/stores/fullscreen.ts b/app/src/lib/stores/fullscreen.ts
index 4307250..1cc8098 100644
--- a/app/src/lib/stores/fullscreen.ts
+++ b/app/src/lib/stores/fullscreen.ts
@@ -1,24 +1,24 @@
-import { writable } from 'svelte/store';
+import { writable } from 'svelte/store'
-export const isFullscreen = writable(false);
+export const isFullscreen = writable(false)
export function toggleFullscreen() {
- isFullscreen.update((state) => {
- !state ? document.documentElement.requestFullscreen() : document.exitFullscreen();
- return !state;
- });
+ isFullscreen.update(state => {
+ !state ? document.documentElement.requestFullscreen() : document.exitFullscreen()
+ return !state
+ })
}
export function enterFullscreen() {
- if (!document.fullscreenElement) {
- document.documentElement.requestFullscreen();
- isFullscreen.set(true);
- }
+ if (!document.fullscreenElement) {
+ document.documentElement.requestFullscreen()
+ isFullscreen.set(true)
+ }
}
export function exitFullscreen() {
- if (document.fullscreenElement) {
- document.exitFullscreen();
- isFullscreen.set(false);
- }
+ if (document.fullscreenElement) {
+ document.exitFullscreen()
+ isFullscreen.set(false)
+ }
}
diff --git a/app/src/lib/stores/gamepad.ts b/app/src/lib/stores/gamepad.ts
index bc311d2..5b82f7f 100644
--- a/app/src/lib/stores/gamepad.ts
+++ b/app/src/lib/stores/gamepad.ts
@@ -1,40 +1,40 @@
import { readable, derived } from 'svelte/store'
export type GamepadState = {
- available: boolean
- gamepads: Gamepad[]
+ available: boolean
+ gamepads: Gamepad[]
}
export const gamepads = readable({ available: false, gamepads: [] }, set => {
- const update = () => {
- const hasGamepadAPI = 'getGamepads' in navigator
- if (!hasGamepadAPI) {
- set({ available: false, gamepads: [] })
- return
+ const update = () => {
+ const hasGamepadAPI = 'getGamepads' in navigator
+ if (!hasGamepadAPI) {
+ set({ available: false, gamepads: [] })
+ return
+ }
+
+ const gps = navigator.getGamepads?.() ?? []
+ const validGamepads = gps.filter(Boolean) as Gamepad[]
+ set({
+ available: true,
+ gamepads: validGamepads
+ })
+ raf = requestAnimationFrame(update)
}
- const gps = navigator.getGamepads?.() ?? []
- const validGamepads = gps.filter(Boolean) as Gamepad[]
- set({
- available: true,
- gamepads: validGamepads
- })
- raf = requestAnimationFrame(update)
- }
+ window.addEventListener('gamepadconnected', update)
+ window.addEventListener('gamepaddisconnected', update)
+ let raf = requestAnimationFrame(update)
- window.addEventListener('gamepadconnected', update)
- window.addEventListener('gamepaddisconnected', update)
- let raf = requestAnimationFrame(update)
-
- return () => {
- cancelAnimationFrame(raf)
- window.removeEventListener('gamepadconnected', update)
- window.removeEventListener('gamepaddisconnected', update)
- }
+ return () => {
+ cancelAnimationFrame(raf)
+ window.removeEventListener('gamepadconnected', update)
+ window.removeEventListener('gamepaddisconnected', update)
+ }
})
export const gamepad = derived(gamepads, $gamepads =>
- $gamepads.available && $gamepads.gamepads.length > 0 ? $gamepads.gamepads[0] : null
+ $gamepads.available && $gamepads.gamepads.length > 0 ? $gamepads.gamepads[0] : null
)
export const gamepadAxes = derived(gamepad, $gamepad => $gamepad?.axes ?? [0, 0, 0, 0])
@@ -42,6 +42,6 @@ export const gamepadAxes = derived(gamepad, $gamepad => $gamepad?.axes ?? [0, 0,
export const gamepadButtons = derived(gamepad, $gamepad => $gamepad?.buttons ?? [])
export const hasGamepad = derived(
- gamepads,
- $gamepads => $gamepads.available && $gamepads.gamepads.length > 0
+ gamepads,
+ $gamepads => $gamepads.available && $gamepads.gamepads.length > 0
)
diff --git a/app/src/lib/stores/imu.ts b/app/src/lib/stores/imu.ts
index 55fcc1c..12ee5e1 100644
--- a/app/src/lib/stores/imu.ts
+++ b/app/src/lib/stores/imu.ts
@@ -1,7 +1,7 @@
-import { writable } from 'svelte/store';
-import type { IMU } from '$lib/types/models';
+import { writable } from 'svelte/store'
+import type { IMU } from '$lib/types/models'
-const maxIMUData = 100;
+const maxIMUData = 100
export const imu = (() => {
const { subscribe, update } = writable({
@@ -12,16 +12,16 @@ export const imu = (() => {
altitude: [] as number[],
pressure: [] as number[],
bmp_temp: [] as number[]
- });
+ })
const addData = (content: IMU) => {
update(data => {
- (Object.keys(content) as (keyof IMU)[]).forEach(key => {
- data[key] = [...data[key], content[key]].slice(-maxIMUData);
- });
- return data;
- });
- };
+ ;(Object.keys(content) as (keyof IMU)[]).forEach(key => {
+ data[key] = [...data[key], content[key]].slice(-maxIMUData)
+ })
+ return data
+ })
+ }
- return { subscribe, addData };
-})();
+ return { subscribe, addData }
+})()
diff --git a/app/src/lib/stores/index.ts b/app/src/lib/stores/index.ts
index ba46074..8ab16e5 100644
--- a/app/src/lib/stores/index.ts
+++ b/app/src/lib/stores/index.ts
@@ -1,9 +1,9 @@
-export * from './socket-store';
-export * from './logging-store';
-export * from './model-store';
-export * from './socket';
-export * from './fullscreen';
-export * from './telemetry';
-export * from './analytics';
-export * from './featureFlags';
-export * from './location-store';
+export * from './socket-store'
+export * from './logging-store'
+export * from './model-store'
+export * from './socket'
+export * from './fullscreen'
+export * from './telemetry'
+export * from './analytics'
+export * from './featureFlags'
+export * from './location-store'
diff --git a/app/src/lib/stores/location-store.ts b/app/src/lib/stores/location-store.ts
index ad1060e..d0d044f 100644
--- a/app/src/lib/stores/location-store.ts
+++ b/app/src/lib/stores/location-store.ts
@@ -1,5 +1,5 @@
-import { persistentStore } from '$lib/utilities';
-import { writable } from 'svelte/store';
-import { PUBLIC_VITE_USE_HOST_NAME } from '$env/static/public';
+import { persistentStore } from '$lib/utilities'
+import { writable } from 'svelte/store'
+import { PUBLIC_VITE_USE_HOST_NAME } from '$env/static/public'
-export const location = PUBLIC_VITE_USE_HOST_NAME ? writable('') : persistentStore('location', '');
+export const location = PUBLIC_VITE_USE_HOST_NAME ? writable('') : persistentStore('location', '')
diff --git a/app/src/lib/stores/logging-store.ts b/app/src/lib/stores/logging-store.ts
index ef01666..bc29c74 100644
--- a/app/src/lib/stores/logging-store.ts
+++ b/app/src/lib/stores/logging-store.ts
@@ -1,11 +1,11 @@
-import { writable, type Writable } from 'svelte/store';
+import { writable, type Writable } from 'svelte/store'
export interface errorLog {
- message: unknown;
- tag?: string;
- exception?: unknown;
+ message: unknown
+ tag?: string
+ exception?: unknown
}
-export const latestErrorLog: Writable = writable();
+export const latestErrorLog: Writable = writable()
-export const errorLogs: Writable = writable([]);
+export const errorLogs: Writable = writable([])
diff --git a/app/src/lib/stores/model-store.ts b/app/src/lib/stores/model-store.ts
index c1ac068..c8f3cd7 100644
--- a/app/src/lib/stores/model-store.ts
+++ b/app/src/lib/stores/model-store.ts
@@ -13,28 +13,28 @@ export const modes = ['deactivated', 'idle', 'calibration', 'rest', 'stand', 'wa
export type Modes = (typeof modes)[number]
export enum ModesEnum {
- Deactivated = 0,
- Idle = 1,
- Calibration = 2,
- Rest = 3,
- Stand = 4,
- Walk = 5
+ Deactivated = 0,
+ Idle = 1,
+ Calibration = 2,
+ Rest = 3,
+ Stand = 4,
+ Walk = 5
}
export enum WalkGaits {
- Trot = 0,
- Crawl = 1
+ Trot = 0,
+ Crawl = 1
}
export const walkGaits = ['trot', 'crawl'] as const
export const walkGaitLabels: Record = {
- [WalkGaits.Trot]: 'Trot',
- [WalkGaits.Crawl]: 'Crawl'
+ [WalkGaits.Trot]: 'Trot',
+ [WalkGaits.Crawl]: 'Crawl'
}
export const walkGaitToMode = (gait: WalkGaits): 'trot' | 'crawl' => {
- return gait === WalkGaits.Trot ? 'trot' : 'crawl'
+ return gait === WalkGaits.Trot ? 'trot' : 'crawl'
}
export const mode: Writable = writable(ModesEnum.Deactivated)
@@ -46,9 +46,9 @@ export const outControllerData = writable([0, 0, 0, 0, 0, 1, 0])
export const kinematicData = writable([0, 0, 0, 0, 1, 0])
export const input: Writable = writable({
- left: { x: 0, y: 0 },
- right: { x: 0, y: 0 },
- height: 0.5,
- speed: 0.5,
- s1: 0.05
+ left: { x: 0, y: 0 },
+ right: { x: 0, y: 0 },
+ height: 0.5,
+ speed: 0.5,
+ s1: 0.05
})
diff --git a/app/src/lib/stores/socket-store.ts b/app/src/lib/stores/socket-store.ts
index f5faf54..c84a903 100644
--- a/app/src/lib/stores/socket-store.ts
+++ b/app/src/lib/stores/socket-store.ts
@@ -1,27 +1,27 @@
-import { writable, type Writable } from 'svelte/store';
-import { type angles } from '$lib/types/models';
+import { writable, type Writable } from 'svelte/store'
+import { type angles } from '$lib/types/models'
export const servoAnglesOut: Writable = writable([
- 0, 45, -90, 0, 45, -90, 0, 45, -90, 0, 45, -90
-]);
+ 0, 45, -90, 0, 45, -90, 0, 45, -90, 0, 45, -90
+])
export const servoAngles: Writable = writable([
- 0, 45, -90, 0, 45, -90, 0, 45, -90, 0, 45, -90
-]);
-export const logs = writable([] as string[]);
-export const mpu = writable({ heading: 0 });
-export const sonar = writable([0, 0]);
-export const distances = writable({});
+ 0, 45, -90, 0, 45, -90, 0, 45, -90, 0, 45, -90
+])
+export const logs = writable([] as string[])
+export const mpu = writable({ heading: 0 })
+export const sonar = writable([0, 0])
+export const distances = writable({})
export interface socketDataCollection {
- angles: Writable;
- logs: Writable;
- mpu: Writable;
- distances: Writable;
+ angles: Writable
+ logs: Writable
+ mpu: Writable
+ distances: Writable
}
export const socketData = {
- angles: servoAngles,
- logs,
- mpu,
- distances
-};
+ angles: servoAngles,
+ logs,
+ mpu,
+ distances
+}
diff --git a/app/src/lib/stores/socket.ts b/app/src/lib/stores/socket.ts
index 716eea3..bf976d9 100644
--- a/app/src/lib/stores/socket.ts
+++ b/app/src/lib/stores/socket.ts
@@ -1,160 +1,160 @@
-import { writable } from 'svelte/store';
-import { encode, decode } from '@msgpack/msgpack';
+import { writable } from 'svelte/store'
+import { encode, decode } from '@msgpack/msgpack'
-const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const;
-type SocketEvent = (typeof socketEvents)[number];
+const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const
+type SocketEvent = (typeof socketEvents)[number]
-type SocketMessage = [number, string?, unknown?];
+type SocketMessage = [number, string?, unknown?]
-let useBinary = false;
+let useBinary = false
const decodeMessage = (data: string | ArrayBuffer): SocketMessage | null => {
- useBinary = data instanceof ArrayBuffer;
+ useBinary = data instanceof ArrayBuffer
- try {
- if (useBinary) {
- return decode(new Uint8Array(data as ArrayBuffer)) as SocketMessage;
- }
- return JSON.parse(data as string);
- } catch (error) {
- console.error(`Could not decode data: ${new Uint8Array(data as ArrayBuffer)} - ${error}`);
- }
- return null;
-};
-
-const encodeMessage = (data: unknown) => {
- try {
- return useBinary ? encode(data) : JSON.stringify(data);
- } catch (error) {
- console.error(`Could not encode data: ${data} - ${error}`);
- }
-};
-
-function createWebSocket() {
- const listeners = new Map void>>();
- const { subscribe, set } = writable(false);
- const reconnectTimeoutTime = 5000;
- let unresponsiveTimeoutId: ReturnType;
- let reconnectTimeoutId: ReturnType;
- let ws: WebSocket;
- let socketUrl: string | URL;
-
- function init(url: string | URL) {
- socketUrl = url;
- connect();
- }
-
- function disconnect(reason: SocketEvent, event?: Event) {
- ws.close();
- set(false);
- clearTimeout(unresponsiveTimeoutId);
- clearTimeout(reconnectTimeoutId);
- listeners.get(reason)?.forEach(listener => listener(event));
- reconnectTimeoutId = setTimeout(connect, reconnectTimeoutTime);
- }
-
- function connect() {
- ws = new WebSocket(socketUrl);
- ws.binaryType = 'arraybuffer';
- ws.onopen = ev => {
- ping();
- useBinary = true;
- ping();
- set(true);
- clearTimeout(reconnectTimeoutId);
- listeners.get('open')?.forEach(listener => listener(ev));
- for (const event of listeners.keys()) {
- if (socketEvents.includes(event as SocketEvent)) continue;
- subscribeToEvent(event);
- }
- };
- ws.onmessage = frame => {
- resetUnresponsiveCheck();
- const message = decodeMessage(frame.data);
- if (!message) return;
- const [, event, payload = undefined] = message;
- if (event) listeners.get(event)?.forEach(listener => listener(payload));
- };
- ws.onerror = ev => disconnect('error', ev);
- ws.onclose = ev => disconnect('close', ev);
- }
-
- function unsubscribe(event: string, listener?: (data: unknown) => void) {
- const eventListeners = listeners.get(event);
- if (!eventListeners) return;
-
- if (!eventListeners.size) {
- unsubscribeToEvent(event);
- }
- if (listener) {
- eventListeners?.delete(listener);
- } else {
- listeners.delete(event);
- }
- }
-
- function resetUnresponsiveCheck() {
- clearTimeout(unresponsiveTimeoutId);
- unresponsiveTimeoutId = setTimeout(() => disconnect('unresponsive'), reconnectTimeoutTime);
- }
-
- function sendEvent(event: string, data: unknown) {
- if (!ws || ws.readyState !== WebSocket.OPEN) return;
- send([2, event, data]);
- }
-
- function unsubscribeToEvent(event: string) {
- if (!ws || ws.readyState !== WebSocket.OPEN) return;
- send([1, event]);
- }
-
- function subscribeToEvent(event: string) {
- if (!ws || ws.readyState !== WebSocket.OPEN) return;
- send([0, event]);
- }
-
- function send(data: unknown) {
- if (!ws || ws.readyState !== WebSocket.OPEN) return;
- const serialized = encodeMessage(data);
- if (!serialized) {
- console.error('Could not serialize data:', data);
- return;
- }
- ws.send(serialized);
- }
-
- function ping() {
- const serialized = encodeMessage([4]);
- if (!serialized) {
- console.error('Could not serialize message');
- return;
- }
- ws.send(serialized);
- }
-
- return {
- subscribe,
- sendEvent,
- init,
- on: (event: string, listener: (data: T) => void): (() => void) => {
- let eventListeners = listeners.get(event);
- if (!eventListeners) {
- if (!socketEvents.includes(event as SocketEvent)) {
- subscribeToEvent(event);
+ try {
+ if (useBinary) {
+ return decode(new Uint8Array(data as ArrayBuffer)) as SocketMessage
}
- eventListeners = new Set();
- listeners.set(event, eventListeners);
- }
- eventListeners.add(listener as (data: unknown) => void);
-
- return () => {
- unsubscribe(event, listener as (data: unknown) => void);
- };
- },
- off: (event: string, listener?: (data: T) => void) => {
- unsubscribe(event, listener as (data: unknown) => void);
- },
- };
+ return JSON.parse(data as string)
+ } catch (error) {
+ console.error(`Could not decode data: ${new Uint8Array(data as ArrayBuffer)} - ${error}`)
+ }
+ return null
}
-export const socket = createWebSocket();
+const encodeMessage = (data: unknown) => {
+ try {
+ return useBinary ? encode(data) : JSON.stringify(data)
+ } catch (error) {
+ console.error(`Could not encode data: ${data} - ${error}`)
+ }
+}
+
+function createWebSocket() {
+ const listeners = new Map void>>()
+ const { subscribe, set } = writable(false)
+ const reconnectTimeoutTime = 5000
+ let unresponsiveTimeoutId: ReturnType
+ let reconnectTimeoutId: ReturnType
+ let ws: WebSocket
+ let socketUrl: string | URL
+
+ function init(url: string | URL) {
+ socketUrl = url
+ connect()
+ }
+
+ function disconnect(reason: SocketEvent, event?: Event) {
+ ws.close()
+ set(false)
+ clearTimeout(unresponsiveTimeoutId)
+ clearTimeout(reconnectTimeoutId)
+ listeners.get(reason)?.forEach(listener => listener(event))
+ reconnectTimeoutId = setTimeout(connect, reconnectTimeoutTime)
+ }
+
+ function connect() {
+ ws = new WebSocket(socketUrl)
+ ws.binaryType = 'arraybuffer'
+ ws.onopen = ev => {
+ ping()
+ useBinary = true
+ ping()
+ set(true)
+ clearTimeout(reconnectTimeoutId)
+ listeners.get('open')?.forEach(listener => listener(ev))
+ for (const event of listeners.keys()) {
+ if (socketEvents.includes(event as SocketEvent)) continue
+ subscribeToEvent(event)
+ }
+ }
+ ws.onmessage = frame => {
+ resetUnresponsiveCheck()
+ const message = decodeMessage(frame.data)
+ if (!message) return
+ const [, event, payload = undefined] = message
+ if (event) listeners.get(event)?.forEach(listener => listener(payload))
+ }
+ ws.onerror = ev => disconnect('error', ev)
+ ws.onclose = ev => disconnect('close', ev)
+ }
+
+ function unsubscribe(event: string, listener?: (data: unknown) => void) {
+ const eventListeners = listeners.get(event)
+ if (!eventListeners) return
+
+ if (!eventListeners.size) {
+ unsubscribeToEvent(event)
+ }
+ if (listener) {
+ eventListeners?.delete(listener)
+ } else {
+ listeners.delete(event)
+ }
+ }
+
+ function resetUnresponsiveCheck() {
+ clearTimeout(unresponsiveTimeoutId)
+ unresponsiveTimeoutId = setTimeout(() => disconnect('unresponsive'), reconnectTimeoutTime)
+ }
+
+ function sendEvent(event: string, data: unknown) {
+ if (!ws || ws.readyState !== WebSocket.OPEN) return
+ send([2, event, data])
+ }
+
+ function unsubscribeToEvent(event: string) {
+ if (!ws || ws.readyState !== WebSocket.OPEN) return
+ send([1, event])
+ }
+
+ function subscribeToEvent(event: string) {
+ if (!ws || ws.readyState !== WebSocket.OPEN) return
+ send([0, event])
+ }
+
+ function send(data: unknown) {
+ if (!ws || ws.readyState !== WebSocket.OPEN) return
+ const serialized = encodeMessage(data)
+ if (!serialized) {
+ console.error('Could not serialize data:', data)
+ return
+ }
+ ws.send(serialized)
+ }
+
+ function ping() {
+ const serialized = encodeMessage([4])
+ if (!serialized) {
+ console.error('Could not serialize message')
+ return
+ }
+ ws.send(serialized)
+ }
+
+ return {
+ subscribe,
+ sendEvent,
+ init,
+ on: (event: string, listener: (data: T) => void): (() => void) => {
+ let eventListeners = listeners.get(event)
+ if (!eventListeners) {
+ if (!socketEvents.includes(event as SocketEvent)) {
+ subscribeToEvent(event)
+ }
+ eventListeners = new Set()
+ listeners.set(event, eventListeners)
+ }
+ eventListeners.add(listener as (data: unknown) => void)
+
+ return () => {
+ unsubscribe(event, listener as (data: unknown) => void)
+ }
+ },
+ off: (event: string, listener?: (data: T) => void) => {
+ unsubscribe(event, listener as (data: unknown) => void)
+ }
+ }
+}
+
+export const socket = createWebSocket()
diff --git a/app/src/lib/stores/telemetry.ts b/app/src/lib/stores/telemetry.ts
index 4dfeb1b..c197506 100644
--- a/app/src/lib/stores/telemetry.ts
+++ b/app/src/lib/stores/telemetry.ts
@@ -1,5 +1,5 @@
-import type { DownloadOTA } from '$lib/types/models';
-import { writable } from 'svelte/store';
+import type { DownloadOTA } from '$lib/types/models'
+import { writable } from 'svelte/store'
let telemetry_data = {
rssi: {
@@ -10,10 +10,10 @@ let telemetry_data = {
progress: 0,
error: ''
}
-};
+}
function createTelemetry() {
- const { subscribe, set, update } = writable(telemetry_data);
+ const { subscribe, set, update } = writable(telemetry_data)
return {
subscribe,
@@ -21,15 +21,15 @@ function createTelemetry() {
update(telemetry_data => ({
...telemetry_data,
rssi: { rssi: data }
- }));
+ }))
},
setDownloadOTA: (data: DownloadOTA) => {
update(telemetry_data => ({
...telemetry_data,
download_ota: { status: data.status, progress: data.progress, error: data.error }
- }));
+ }))
}
- };
+ }
}
-export const telemetry = createTelemetry();
+export const telemetry = createTelemetry()
diff --git a/app/src/lib/types/mathutils.d.ts b/app/src/lib/types/mathutils.d.ts
index ac1fb63..01a3889 100644
--- a/app/src/lib/types/mathutils.d.ts
+++ b/app/src/lib/types/mathutils.d.ts
@@ -1,17 +1,17 @@
declare module 'three/src/math/MathUtils' {
- export function generateUUID(): string;
- export function clamp(value: number, min: number, max: number): number;
- export function euclideanModulo(n: number, m: number): number;
- export function mapLinear(x: number, a1: number, a2: number, b1: number, b2: number): number;
- export function lerp(x: number, y: number, t: number): number;
- export function smoothstep(x: number, min: number, max: number): number;
- export function smootherstep(x: number, min: number, max: number): number;
- export function randInt(low: number, high: number): number;
- export function randFloat(low: number, high: number): number;
- export function randFloatSpread(range: number): number;
- export function degToRad(degrees: number): number;
- export function radToDeg(radians: number): number;
- export function isPowerOfTwo(value: number): boolean;
- export function ceilPowerOfTwo(value: number): number;
- export function floorPowerOfTwo(value: number): number;
+ export function generateUUID(): string
+ export function clamp(value: number, min: number, max: number): number
+ export function euclideanModulo(n: number, m: number): number
+ export function mapLinear(x: number, a1: number, a2: number, b1: number, b2: number): number
+ export function lerp(x: number, y: number, t: number): number
+ export function smoothstep(x: number, min: number, max: number): number
+ export function smootherstep(x: number, min: number, max: number): number
+ export function randInt(low: number, high: number): number
+ export function randFloat(low: number, high: number): number
+ export function randFloatSpread(range: number): number
+ export function degToRad(degrees: number): number
+ export function radToDeg(radians: number): number
+ export function isPowerOfTwo(value: number): boolean
+ export function ceilPowerOfTwo(value: number): number
+ export function floorPowerOfTwo(value: number): number
}
diff --git a/app/src/lib/types/models.ts b/app/src/lib/types/models.ts
index cfc42a1..616b222 100644
--- a/app/src/lib/types/models.ts
+++ b/app/src/lib/types/models.ts
@@ -1,239 +1,239 @@
export enum MessageTopic {
- imu = 'imu',
- mode = 'mode',
- input = 'input',
- analytics = 'analytics',
- position = 'position',
- angles = 'angles',
- i2cScan = 'i2cScan',
- peripheralSettings = 'peripheralSettings',
- otastatus = 'otastatus',
- gait = 'walk_gait',
- servoState = 'servoState',
- servoPWM = 'servoPWM',
- WiFiSettings = 'WiFiSettings',
- sonar = 'sonar',
- rssi = 'rssi'
+ imu = 'imu',
+ mode = 'mode',
+ input = 'input',
+ analytics = 'analytics',
+ position = 'position',
+ angles = 'angles',
+ i2cScan = 'i2cScan',
+ peripheralSettings = 'peripheralSettings',
+ otastatus = 'otastatus',
+ gait = 'walk_gait',
+ servoState = 'servoState',
+ servoPWM = 'servoPWM',
+ WiFiSettings = 'WiFiSettings',
+ sonar = 'sonar',
+ rssi = 'rssi'
}
export type vector = { x: number; y: number }
export interface ControllerInput {
- left: vector
- right: vector
- height: number
- speed: number
- s1: number
+ left: vector
+ right: vector
+ height: number
+ speed: number
+ s1: number
}
export type GithubRelease = {
- message: string
- tag_name: string
- assets: Array<{
- name: string
- browser_download_url: string
- }>
+ message: string
+ tag_name: string
+ assets: Array<{
+ name: string
+ browser_download_url: string
+ }>
}
export type angles = number[] | Int16Array
export type WifiStatus = {
- status: number
- local_ip: string
- mac_address: string
- rssi: number
- ssid: string
- bssid: string
- channel: number
- subnet_mask: string
- gateway_ip: string
- dns_ip_1: string
- dns_ip_2?: string
+ status: number
+ local_ip: string
+ mac_address: string
+ rssi: number
+ ssid: string
+ bssid: string
+ channel: number
+ subnet_mask: string
+ gateway_ip: string
+ dns_ip_1: string
+ dns_ip_2?: string
}
export type WifiSettings = {
- hostname: string
- priority_RSSI: boolean
- wifi_networks: KnownNetworkItem[]
+ hostname: string
+ priority_RSSI: boolean
+ wifi_networks: KnownNetworkItem[]
}
export type NetworkList = {
- networks: NetworkItem[]
+ networks: NetworkItem[]
}
export type KnownNetworkItem = {
- ssid: string
- password: string
- static_ip_config: boolean
- local_ip?: string
- subnet_mask?: string
- gateway_ip?: string
- dns_ip_1?: string
- dns_ip_2?: string
+ ssid: string
+ password: string
+ static_ip_config: boolean
+ local_ip?: string
+ subnet_mask?: string
+ gateway_ip?: string
+ dns_ip_1?: string
+ dns_ip_2?: string
}
export type NetworkItem = {
- rssi: number
- ssid: string
- bssid: string
- channel: number
- encryption_type: number
+ rssi: number
+ ssid: string
+ bssid: string
+ channel: number
+ encryption_type: number
}
export type ApStatus = {
- status: number
- ip_address: string
- mac_address: string
- station_num: number
+ status: number
+ ip_address: string
+ mac_address: string
+ station_num: number
}
export type ApSettings = {
- provision_mode: number
- ssid: string
- password: string
- channel: number
- ssid_hidden: boolean
- max_clients: number
- local_ip: string
- gateway_ip: string
- subnet_mask: string
+ provision_mode: number
+ ssid: string
+ password: string
+ channel: number
+ ssid_hidden: boolean
+ max_clients: number
+ local_ip: string
+ gateway_ip: string
+ subnet_mask: string
}
export type DownloadOTA = {
- status: string
- progress: number
- error: string
+ status: string
+ progress: number
+ error: string
}
export type Analytics = {
- max_alloc_heap: number
- psram_size: number
- free_psram: number
- free_heap: number
- total_heap: number
- min_free_heap: number
- core_temp: number
- fs_total: number
- fs_used: number
- uptime: number
- cpu0_usage: number
- cpu1_usage: number
- cpu_usage: number
+ max_alloc_heap: number
+ psram_size: number
+ free_psram: number
+ free_heap: number
+ total_heap: number
+ min_free_heap: number
+ core_temp: number
+ fs_total: number
+ fs_used: number
+ uptime: number
+ cpu0_usage: number
+ cpu1_usage: number
+ cpu_usage: number
}
export type Rssi = {
- rssi: number
- ssid: string
+ rssi: number
+ ssid: string
}
export type StaticSystemInformation = {
- esp_platform: string
- firmware_version: string
- cpu_freq_mhz: number
- cpu_type: string
- cpu_rev: number
- cpu_cores: number
- sketch_size: number
- free_sketch_space: number
- sdk_version: string
- arduino_version: string
- flash_chip_size: number
- flash_chip_speed: number
- cpu_reset_reason: string
+ esp_platform: string
+ firmware_version: string
+ cpu_freq_mhz: number
+ cpu_type: string
+ cpu_rev: number
+ cpu_cores: number
+ sketch_size: number
+ free_sketch_space: number
+ sdk_version: string
+ arduino_version: string
+ flash_chip_size: number
+ flash_chip_speed: number
+ cpu_reset_reason: string
}
export type SystemInformation = Analytics & StaticSystemInformation
export type IMU = {
- x: number
- y: number
- z: number
- heading: number
- altitude: number
- bmp_temp: number
- pressure: number
+ x: number
+ y: number
+ z: number
+ heading: number
+ altitude: number
+ bmp_temp: number
+ pressure: number
}
export interface I2CDevice {
- address: number
- part_number: string
- name: string
+ address: number
+ part_number: string
+ name: string
}
export type PinConfig = {
- pin: number
- mode: string
- type: string
- role: string
+ pin: number
+ mode: string
+ type: string
+ role: string
}
export type PeripheralsConfiguration = {
- sda: number
- scl: number
- frequency: number
- pins: PinConfig[]
+ sda: number
+ scl: number
+ frequency: number
+ pins: PinConfig[]
}
export type CameraSettings = {
- framesize: number
- quality: number
- brightness: number
- contrast: number
- saturation: number
- sharpness: number
- denoise: number
- special_effect: number
- wb_mode: number
- vflip: boolean
- hmirror: boolean
+ framesize: number
+ quality: number
+ brightness: number
+ contrast: number
+ saturation: number
+ sharpness: number
+ denoise: number
+ special_effect: number
+ wb_mode: number
+ vflip: boolean
+ hmirror: boolean
}
export type File = number
export interface Directory {
- [key: string]: File | Directory
+ [key: string]: File | Directory
}
export type Servo = {
- name: string
- channel: number
- inverted: boolean
- angle: number
- center_angle: number
+ name: string
+ channel: number
+ inverted: boolean
+ angle: number
+ center_angle: number
}
export type ServoConfiguration = {
- is_active: boolean
- servo_pwm_frequency: number
- servo_oscillator_frequency: number
- servos: Servo[]
+ is_active: boolean
+ servo_pwm_frequency: number
+ servo_oscillator_frequency: number
+ servos: Servo[]
}
export interface MDNSServiceQuery {
- services: MDNSServiceItem[]
+ services: MDNSServiceItem[]
}
export interface MDNSServiceItem {
- ip: string
- port: number
- name: string
+ ip: string
+ port: number
+ name: string
}
export interface MDNSService {
- service: string
- protocol: string
- port: number
+ service: string
+ protocol: string
+ port: number
}
export interface MDNSTxtRecord {
- key: string
- value: string
+ key: string
+ value: string
}
export interface MDNSStatus {
- started: boolean
- hostname: string
- instance: string
- services: MDNSService[]
- global_txt_records: MDNSTxtRecord[]
+ started: boolean
+ hostname: string
+ instance: string
+ services: MDNSService[]
+ global_txt_records: MDNSTxtRecord[]
}
diff --git a/app/src/lib/types/uzip.d.ts b/app/src/lib/types/uzip.d.ts
index 05e744c..49b3d72 100644
--- a/app/src/lib/types/uzip.d.ts
+++ b/app/src/lib/types/uzip.d.ts
@@ -1,14 +1,14 @@
declare module 'uzip' {
- interface UZIP {
- parse(data: Uint8Array | ArrayBuffer): any;
- compress(data: any): Uint8Array | ArrayBuffer;
- compressRaw(data: Uint8Array | ArrayBuffer): Uint8Array | ArrayBuffer;
- decompress(data: Uint8Array | ArrayBuffer): any;
- decompressRaw(data: Uint8Array | ArrayBuffer): Uint8Array | ArrayBuffer;
- encode(data: any): Uint8Array | ArrayBuffer;
- decode(data: Uint8Array | ArrayBuffer): any;
- }
+ interface UZIP {
+ parse(data: Uint8Array | ArrayBuffer): any
+ compress(data: any): Uint8Array | ArrayBuffer
+ compressRaw(data: Uint8Array | ArrayBuffer): Uint8Array | ArrayBuffer
+ decompress(data: Uint8Array | ArrayBuffer): any
+ decompressRaw(data: Uint8Array | ArrayBuffer): Uint8Array | ArrayBuffer
+ encode(data: any): Uint8Array | ArrayBuffer
+ decode(data: Uint8Array | ArrayBuffer): any
+ }
- const uzip: UZIP;
- export default uzip;
+ const uzip: UZIP
+ export default uzip
}
diff --git a/app/src/lib/utilities/buffer-utilities.ts b/app/src/lib/utilities/buffer-utilities.ts
index a5f5182..5a62dbe 100644
--- a/app/src/lib/utilities/buffer-utilities.ts
+++ b/app/src/lib/utilities/buffer-utilities.ts
@@ -1,15 +1,15 @@
export class throttler {
- private _throttlePause: boolean;
- constructor() {
- this._throttlePause = false;
- }
- throttle = (callback: Function, time: number) => {
- if (this._throttlePause) return;
+ private _throttlePause: boolean
+ constructor() {
+ this._throttlePause = false
+ }
+ throttle = (callback: Function, time: number) => {
+ if (this._throttlePause) return
- this._throttlePause = true;
- setTimeout(() => {
- callback();
- this._throttlePause = false;
- }, time);
- };
+ this._throttlePause = true
+ setTimeout(() => {
+ callback()
+ this._throttlePause = false
+ }, time)
+ }
}
diff --git a/app/src/lib/utilities/color-utilities.ts b/app/src/lib/utilities/color-utilities.ts
index 1500cb8..8acb7ef 100644
--- a/app/src/lib/utilities/color-utilities.ts
+++ b/app/src/lib/utilities/color-utilities.ts
@@ -1,6 +1,6 @@
export const daisyColor = (name: string, opacity: number = 100) => {
- const color = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
- if (opacity >= 100) return color;
- const alpha = Math.min(Math.max(opacity, 0), 100) / 100;
- return `${color.replace(/(\/\s*\d+(\.\d+)?\))|\)$/, '')} / ${alpha})`;
-};
+ const color = getComputedStyle(document.documentElement).getPropertyValue(name).trim()
+ if (opacity >= 100) return color
+ const alpha = Math.min(Math.max(opacity, 0), 100) / 100
+ return `${color.replace(/(\/\s*\d+(\.\d+)?\))|\)$/, '')} / ${alpha})`
+}
diff --git a/app/src/lib/utilities/index.ts b/app/src/lib/utilities/index.ts
index acbf139..c6724f7 100644
--- a/app/src/lib/utilities/index.ts
+++ b/app/src/lib/utilities/index.ts
@@ -1,9 +1,9 @@
-export * from './result';
-export * from './string-utilities';
-export * from './svelte-utilities';
-export * from './math-utilities';
-export * from './buffer-utilities';
-export * from './model-utilities';
-export * from './position-utilities';
-export * from './string-utilities';
-export * from './color-utilities';
+export * from './result'
+export * from './string-utilities'
+export * from './svelte-utilities'
+export * from './math-utilities'
+export * from './buffer-utilities'
+export * from './model-utilities'
+export * from './position-utilities'
+export * from './string-utilities'
+export * from './color-utilities'
diff --git a/app/src/lib/utilities/math-utilities.ts b/app/src/lib/utilities/math-utilities.ts
index ec84267..2a48f6c 100644
--- a/app/src/lib/utilities/math-utilities.ts
+++ b/app/src/lib/utilities/math-utilities.ts
@@ -1,18 +1,18 @@
export const toUint8 = (number: number, min: number, max: number) => {
- number = Math.max(min, Math.min(max, number));
- let scaled = ((number - min) / (max - min)) * 255;
- return Math.round(scaled) & 0xff;
-};
+ number = Math.max(min, Math.min(max, number))
+ let scaled = ((number - min) / (max - min)) * 255
+ return Math.round(scaled) & 0xff
+}
export const toInt8 = (number: number, min: number, max: number) => {
- number = Math.max(min, Math.min(max, number));
- let scaled = ((number - min) / (max - min)) * 255 - 128;
- return Math.max(-128, Math.min(127, Math.round(scaled))) | 0;
-};
+ number = Math.max(min, Math.min(max, number))
+ let scaled = ((number - min) / (max - min)) * 255 - 128
+ return Math.max(-128, Math.min(127, Math.round(scaled))) | 0
+}
export const fromInt8 = (int8: number, min: number, max: number) => {
- int8 = Math.max(-128, Math.min(127, int8));
- const scaled = (int8 + 128) / 255;
- const number = scaled * (max - min) + min;
- return number;
-};
\ No newline at end of file
+ int8 = Math.max(-128, Math.min(127, int8))
+ const scaled = (int8 + 128) / 255
+ const number = scaled * (max - min) + min
+ return number
+}
diff --git a/app/src/lib/utilities/model-utilities.ts b/app/src/lib/utilities/model-utilities.ts
index c339865..771e927 100644
--- a/app/src/lib/utilities/model-utilities.ts
+++ b/app/src/lib/utilities/model-utilities.ts
@@ -10,84 +10,85 @@ import { get } from 'svelte/store'
let model_xml: XMLDocument
export const populateModelCache = async () => {
- await cacheModelFiles()
- const modelRes = await loadModel(get(currentVariant).model)
- if (modelRes.isOk()) {
- const [urdf, JOINT_NAME] = modelRes.inner
- jointNames.set(JOINT_NAME)
- model.set(urdf)
- } else {
- console.error(modelRes.inner, { exception: modelRes.exception })
- }
+ await cacheModelFiles()
+ const modelRes = await loadModel(get(currentVariant).model)
+ if (modelRes.isOk()) {
+ const [urdf, JOINT_NAME] = modelRes.inner
+ jointNames.set(JOINT_NAME)
+ model.set(urdf)
+ } else {
+ console.error(modelRes.inner, { exception: modelRes.exception })
+ }
}
export const cacheModelFiles = async () => {
- const data = await fetch(get(currentVariant).stl)
+ const data = await fetch(get(currentVariant).stl)
- const files = uzip.parse(await data.arrayBuffer())
+ const files = uzip.parse(await data.arrayBuffer())
- for (const [path, data] of Object.entries(files) as [path: string, data: Uint8Array][]) {
- const url = new URL(path, window.location.href)
- fileService?.saveFile(url.toString(), data)
- }
+ for (const [path, data] of Object.entries(files) as [path: string, data: Uint8Array][]) {
+ const url = new URL(path, window.location.href)
+ fileService?.saveFile(url.toString(), data)
+ }
}
export const loadModel = async (url: string): Promise> => {
- const urdfLoader = new URDFLoader()
- urdfLoader.workingPath = LoaderUtils.extractUrlBase(url)
+ const urdfLoader = new URDFLoader()
+ urdfLoader.workingPath = LoaderUtils.extractUrlBase(url)
- let xml = url.endsWith('.xacro') ? await loadXacro(url) : await fetch(url).then(res => res.text())
+ let xml =
+ url.endsWith('.xacro') ? await loadXacro(url) : await fetch(url).then(res => res.text())
- if (typeof xml === 'string') {
- xml = new window.DOMParser().parseFromString(xml, 'text/xml')
- }
-
- return new Promise(resolve => {
- model_xml = xml
- try {
- const model = urdfLoader.parse(xml)
- setupRobot(model)
- const joints = Object.entries(model.joints)
- .filter(joint => joint[1].jointType !== 'fixed')
- .map(joint => joint[0])
-
- resolve(Result.ok([model, joints]))
- } catch (error) {
- resolve(Result.err('Failed to load model', error))
+ if (typeof xml === 'string') {
+ xml = new window.DOMParser().parseFromString(xml, 'text/xml')
}
- })
+
+ return new Promise(resolve => {
+ model_xml = xml
+ try {
+ const model = urdfLoader.parse(xml)
+ setupRobot(model)
+ const joints = Object.entries(model.joints)
+ .filter(joint => joint[1].jointType !== 'fixed')
+ .map(joint => joint[0])
+
+ resolve(Result.ok([model, joints]))
+ } catch (error) {
+ resolve(Result.err('Failed to load model', error))
+ }
+ })
}
const loadXacro = async (url: string): Promise =>
- new Promise((resolve, reject) => {
- new XacroLoader().load(url, resolve, reject)
- })
+ new Promise((resolve, reject) => {
+ new XacroLoader().load(url, resolve, reject)
+ })
function setupRobot(robot: URDFRobot) {
- robot.rotation.x = -Math.PI / 2
- robot.rotation.z = Math.PI / 2
- robot.scale.setScalar(10)
- robot.traverse(c => (c.castShadow = true))
- robot.updateMatrixWorld(true)
+ robot.rotation.x = -Math.PI / 2
+ robot.rotation.z = Math.PI / 2
+ robot.scale.setScalar(10)
+ robot.traverse(c => (c.castShadow = true))
+ robot.updateMatrixWorld(true)
}
export function getToeWorldPositions(robot: URDFRobot): Vector3[] {
- const toes: Vector3[] = []
- robot.traverse(c => {
- if (c.name.includes('toe') && !c.name.includes('_link'))
- toes.push(c.getWorldPosition(new Vector3()))
- })
- return toes
+ const toes: Vector3[] = []
+ robot.traverse(c => {
+ if (c.name.includes('toe') && !c.name.includes('_link'))
+ toes.push(c.getWorldPosition(new Vector3()))
+ })
+ return toes
}
export const extractFootColor = () => {
- const colorElem = model_xml.querySelector('material[name=foot_color] > color') as Element
- const colorAttrStr = colorElem.getAttribute('rgba') as string
- const colorStr = colorAttrStr
- .split(' ')
- .slice(0, 3)
- .map(val => Math.floor(+val * 255))
- .join(', ')
+ const colorElem = model_xml.querySelector('material[name=foot_color] > color') as Element
+ const colorAttrStr = colorElem.getAttribute('rgba') as string
+ const colorStr = colorAttrStr
+ .split(' ')
+ .slice(0, 3)
+ .map(val => Math.floor(+val * 255))
+ .join(', ')
- return new Color(`rgb(${colorStr})`)
+ return new Color(`rgb(${colorStr})`)
}
diff --git a/app/src/lib/utilities/position-utilities.ts b/app/src/lib/utilities/position-utilities.ts
index 00db4a4..8af146d 100644
--- a/app/src/lib/utilities/position-utilities.ts
+++ b/app/src/lib/utilities/position-utilities.ts
@@ -1,84 +1,86 @@
class SunCalculator {
- calculateSunElevation(lat: number = 55, lon: number = 12) {
- const now = new Date();
- const JD = this.getJulianDate(now);
- const solarDec = this.getSolarDeclination(JD);
- const solarTime = this.getSolarTime(now, lon);
+ calculateSunElevation(lat: number = 55, lon: number = 12) {
+ const now = new Date()
+ const JD = this.getJulianDate(now)
+ const solarDec = this.getSolarDeclination(JD)
+ const solarTime = this.getSolarTime(now, lon)
- const hourAngle = (solarTime - 12) * 15;
- const elevation = Math.asin(
- Math.sin(this.degToRad(lat)) * Math.sin(solarDec) +
- Math.cos(this.degToRad(lat)) * Math.cos(solarDec) * Math.cos(this.degToRad(hourAngle))
- );
+ const hourAngle = (solarTime - 12) * 15
+ const elevation = Math.asin(
+ Math.sin(this.degToRad(lat)) * Math.sin(solarDec) +
+ Math.cos(this.degToRad(lat)) *
+ Math.cos(solarDec) *
+ Math.cos(this.degToRad(hourAngle))
+ )
- return this.radToDeg(elevation);
- }
+ return this.radToDeg(elevation)
+ }
- getJulianDate(date: Date) {
- const Y = date.getUTCFullYear();
- const M = date.getUTCMonth() + 1;
- const D =
- date.getUTCDate() +
- date.getUTCHours() / 24 +
- date.getUTCMinutes() / 1440 +
- date.getUTCSeconds() / 86400;
- const A = Math.floor((14 - M) / 12);
- const Y1 = Y + 4800 - A;
- const M1 = M + 12 * A - 3;
- return (
- D +
- Math.floor((153 * M1 + 2) / 5) +
- 365 * Y1 +
- Math.floor(Y1 / 4) -
- Math.floor(Y1 / 100) +
- Math.floor(Y1 / 400) -
- 32045
- );
- }
+ getJulianDate(date: Date) {
+ const Y = date.getUTCFullYear()
+ const M = date.getUTCMonth() + 1
+ const D =
+ date.getUTCDate() +
+ date.getUTCHours() / 24 +
+ date.getUTCMinutes() / 1440 +
+ date.getUTCSeconds() / 86400
+ const A = Math.floor((14 - M) / 12)
+ const Y1 = Y + 4800 - A
+ const M1 = M + 12 * A - 3
+ return (
+ D +
+ Math.floor((153 * M1 + 2) / 5) +
+ 365 * Y1 +
+ Math.floor(Y1 / 4) -
+ Math.floor(Y1 / 100) +
+ Math.floor(Y1 / 400) -
+ 32045
+ )
+ }
- getSolarDeclination(JulianDate: number) {
- const n = JulianDate - 2451545;
- const L = (280.46 + 0.9856474 * n) % 360;
- const g = this.degToRad((357.528 + 0.9856003 * n) % 360);
- const lambda = this.degToRad(L + 1.915 * Math.sin(g) + 0.02 * Math.sin(2 * g));
- return Math.asin(Math.sin(lambda) * Math.sin(this.degToRad(23.44)));
- }
+ getSolarDeclination(JulianDate: number) {
+ const n = JulianDate - 2451545
+ const L = (280.46 + 0.9856474 * n) % 360
+ const g = this.degToRad((357.528 + 0.9856003 * n) % 360)
+ const lambda = this.degToRad(L + 1.915 * Math.sin(g) + 0.02 * Math.sin(2 * g))
+ return Math.asin(Math.sin(lambda) * Math.sin(this.degToRad(23.44)))
+ }
- getSolarTime(date: Date, lon: number) {
- const EoT = this.getEquationOfTime(date);
- const offset = date.getTimezoneOffset() / 60;
- const standardMeridian = Math.round(lon / 15) * 15;
- const solarTime =
- date.getUTCHours() +
- (date.getUTCMinutes() + (4 * (standardMeridian - lon) + EoT)) / 60 -
- offset;
- return (solarTime + 24) % 24;
- }
+ getSolarTime(date: Date, lon: number) {
+ const EoT = this.getEquationOfTime(date)
+ const offset = date.getTimezoneOffset() / 60
+ const standardMeridian = Math.round(lon / 15) * 15
+ const solarTime =
+ date.getUTCHours() +
+ (date.getUTCMinutes() + (4 * (standardMeridian - lon) + EoT)) / 60 -
+ offset
+ return (solarTime + 24) % 24
+ }
- getEquationOfTime(date: Date) {
- const JD = this.getJulianDate(date);
- const n = JD - 2451545;
- const g = this.degToRad((357.528 + 0.9856003 * n) % 360);
- const q = this.degToRad((280.46 + 0.9856474 * n) % 360);
- return (
- 4 *
- this.radToDeg(
- 0.000075 +
- 0.001868 * Math.cos(q) -
- 0.032077 * Math.sin(g) -
- 0.014615 * Math.cos(2 * q) -
- 0.040849 * Math.sin(2 * g)
- )
- );
- }
+ getEquationOfTime(date: Date) {
+ const JD = this.getJulianDate(date)
+ const n = JD - 2451545
+ const g = this.degToRad((357.528 + 0.9856003 * n) % 360)
+ const q = this.degToRad((280.46 + 0.9856474 * n) % 360)
+ return (
+ 4 *
+ this.radToDeg(
+ 0.000075 +
+ 0.001868 * Math.cos(q) -
+ 0.032077 * Math.sin(g) -
+ 0.014615 * Math.cos(2 * q) -
+ 0.040849 * Math.sin(2 * g)
+ )
+ )
+ }
- degToRad(deg: number) {
- return deg * (Math.PI / 180);
- }
+ degToRad(deg: number) {
+ return deg * (Math.PI / 180)
+ }
- radToDeg(rad: number) {
- return rad * (180 / Math.PI);
- }
+ radToDeg(rad: number) {
+ return rad * (180 / Math.PI)
+ }
}
-export const sunCalculator = new SunCalculator();
+export const sunCalculator = new SunCalculator()
diff --git a/app/src/lib/utilities/result/err.ts b/app/src/lib/utilities/result/err.ts
index 8c879be..3e28c8d 100644
--- a/app/src/lib/utilities/result/err.ts
+++ b/app/src/lib/utilities/result/err.ts
@@ -1,42 +1,42 @@
export class Err {
- #inner: T;
- #exception?: U;
+ #inner: T
+ #exception?: U
- constructor(inner: T, exception?: U) {
- this.#inner = inner;
- this.#exception = exception;
- }
+ constructor(inner: T, exception?: U) {
+ this.#inner = inner
+ this.#exception = exception
+ }
- get inner(): T {
- return this.#inner;
- }
+ get inner(): T {
+ return this.#inner
+ }
- get exception(): U | undefined {
- return this.#exception;
- }
+ get exception(): U | undefined {
+ return this.#exception
+ }
- /**
- * Type guard for `Ok`
- * @returns `true` if `Ok`; `false` if `Err`
- */
- isOk(): false {
- return false;
- }
+ /**
+ * Type guard for `Ok`
+ * @returns `true` if `Ok`; `false` if `Err`
+ */
+ isOk(): false {
+ return false
+ }
- /**
- * Type guard for `Err`
- * @returns `true` if `Err`; `false` if `Ok`
- */
- isErr(): this is Err {
- return true;
- }
+ /**
+ * Type guard for `Err`
+ * @returns `true` if `Err`; `false` if `Ok`
+ */
+ isErr(): this is Err {
+ return true
+ }
- /**
- * Create an `Err`
- * @param inner
- * @returns `Err(inner)`
- */
- static new(inner: E, exception: F): Err {
- return new Err(inner, exception);
- }
+ /**
+ * Create an `Err`
+ * @param inner
+ * @returns `Err(inner)`
+ */
+ static new(inner: E, exception: F): Err {
+ return new Err(inner, exception)
+ }
}
diff --git a/app/src/lib/utilities/result/index.ts b/app/src/lib/utilities/result/index.ts
index 55e069b..5130b66 100644
--- a/app/src/lib/utilities/result/index.ts
+++ b/app/src/lib/utilities/result/index.ts
@@ -1,3 +1,3 @@
-export * from './err';
-export * from './ok';
-export * from './result';
+export * from './err'
+export * from './ok'
+export * from './result'
diff --git a/app/src/lib/utilities/result/ok.ts b/app/src/lib/utilities/result/ok.ts
index 868b3c8..d2f83c4 100644
--- a/app/src/lib/utilities/result/ok.ts
+++ b/app/src/lib/utilities/result/ok.ts
@@ -1,44 +1,44 @@
export class Ok {
- #inner: T;
+ #inner: T
- constructor(inner: T) {
- this.#inner = inner;
- }
+ constructor(inner: T) {
+ this.#inner = inner
+ }
- get inner(): T {
- return this.#inner;
- }
+ get inner(): T {
+ return this.#inner
+ }
- /**
- * Type guard for `Ok`
- * @returns `true` if `Ok`; `false` if `Err`
- */
- isOk(): this is Ok {
- return true;
- }
+ /**
+ * Type guard for `Ok`
+ * @returns `true` if `Ok`; `false` if `Err`
+ */
+ isOk(): this is Ok {
+ return true
+ }
- /**
- * Type guard for `Err`
- * @returns `true` if `Err`; `false` if `Ok`
- */
- isErr(): false {
- return false;
- }
+ /**
+ * Type guard for `Err`
+ * @returns `true` if `Err`; `false` if `Ok`
+ */
+ isErr(): false {
+ return false
+ }
- /**
- * Create an `Ok`
- * @param inner
- * @returns `Ok(inner)`
- */
- static new(inner: T): Ok {
- return new Ok(inner);
- }
+ /**
+ * Create an `Ok`
+ * @param inner
+ * @returns `Ok(inner)`
+ */
+ static new(inner: T): Ok {
+ return new Ok(inner)
+ }
- /**
- * Create an empty `Ok`
- * @returns `Ok(void)`
- */
- static void(): Ok {
- return new Ok(undefined);
- }
+ /**
+ * Create an empty `Ok`
+ * @returns `Ok(void)`
+ */
+ static void(): Ok {
+ return new Ok(undefined)
+ }
}
diff --git a/app/src/lib/utilities/result/result.ts b/app/src/lib/utilities/result/result.ts
index 4e86e00..796267c 100644
--- a/app/src/lib/utilities/result/result.ts
+++ b/app/src/lib/utilities/result/result.ts
@@ -1,20 +1,20 @@
-import { Err } from './err';
-import { Ok } from './ok';
+import { Err } from './err'
+import { Ok } from './ok'
-export type Result = Ok | Err;
+export type Result = Ok | Err
export namespace Result {
- /**
- * @returns `Ok`
- */
- export function ok(value: T) {
- return Ok.new(value);
- }
+ /**
+ * @returns `Ok`
+ */
+ export function ok(value: T) {
+ return Ok.new(value)
+ }
- /**
- * @returns `Err`
- */
- export function err(error: E, exception?: F) {
- return Err.new(error, exception);
- }
+ /**
+ * @returns `Err`
+ */
+ export function err(error: E, exception?: F) {
+ return Err.new(error, exception)
+ }
}
diff --git a/app/src/lib/utilities/string-utilities.ts b/app/src/lib/utilities/string-utilities.ts
index e4b6c80..429caa2 100644
--- a/app/src/lib/utilities/string-utilities.ts
+++ b/app/src/lib/utilities/string-utilities.ts
@@ -1,47 +1,47 @@
export const humanFileSize = (size: number): string => {
- const units = ['B', 'kB', 'MB', 'GB', 'TB']
- const i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024))
- return Number((size / Math.pow(1024, i)).toFixed(2)) * 1 + units[i]
+ const units = ['B', 'kB', 'MB', 'GB', 'TB']
+ const i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024))
+ return Number((size / Math.pow(1024, i)).toFixed(2)) * 1 + units[i]
}
export const capitalize = (str: string): string => {
- return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
}
export const convertSeconds = (seconds: number) => {
- // Calculate the number of seconds, minutes, hours, and days
- let minutes = Math.floor(seconds / 60)
- let hours = Math.floor(minutes / 60)
- const days = Math.floor(hours / 24)
+ // Calculate the number of seconds, minutes, hours, and days
+ let minutes = Math.floor(seconds / 60)
+ let hours = Math.floor(minutes / 60)
+ const days = Math.floor(hours / 24)
- // Calculate the remaining hours, minutes, and seconds
- hours = hours % 24
- minutes = minutes % 60
- seconds = seconds % 60
+ // Calculate the remaining hours, minutes, and seconds
+ hours = hours % 24
+ minutes = minutes % 60
+ seconds = seconds % 60
- // Create the formatted string
- let result = ''
- if (days > 0) {
- result += days + ' day' + (days > 1 ? 's' : '') + ' '
- }
- if (hours > 0) {
- result += hours + ' hour' + (hours > 1 ? 's' : '') + ' '
- }
- if (minutes > 0) {
- result += minutes + ' minute' + (minutes > 1 ? 's' : '') + ' '
- }
- result += seconds + ' second' + (seconds > 1 ? 's' : '')
+ // Create the formatted string
+ let result = ''
+ if (days > 0) {
+ result += days + ' day' + (days > 1 ? 's' : '') + ' '
+ }
+ if (hours > 0) {
+ result += hours + ' hour' + (hours > 1 ? 's' : '') + ' '
+ }
+ if (minutes > 0) {
+ result += minutes + ' minute' + (minutes > 1 ? 's' : '') + ' '
+ }
+ result += seconds + ' second' + (seconds > 1 ? 's' : '')
- return result
+ return result
}
export const compareIp = (ip1: string, ip2: string) => {
- const ip1Parts = ip1.split('.').map(Number)
- const ip2Parts = ip2.split('.').map(Number)
- for (let i = 0; i < 4; i++) {
- if (ip1Parts[i] !== ip2Parts[i]) {
- return ip1Parts[i] > ip2Parts[i] ? 1 : -1
+ const ip1Parts = ip1.split('.').map(Number)
+ const ip2Parts = ip2.split('.').map(Number)
+ for (let i = 0; i < 4; i++) {
+ if (ip1Parts[i] !== ip2Parts[i]) {
+ return ip1Parts[i] > ip2Parts[i] ? 1 : -1
+ }
}
- }
- return 0
+ return 0
}
diff --git a/app/src/lib/utilities/svelte-utilities.ts b/app/src/lib/utilities/svelte-utilities.ts
index ba78e77..57b6ba3 100644
--- a/app/src/lib/utilities/svelte-utilities.ts
+++ b/app/src/lib/utilities/svelte-utilities.ts
@@ -1,16 +1,16 @@
-import { writable } from 'svelte/store';
-import { browser } from '$app/environment';
+import { writable } from 'svelte/store'
+import { browser } from '$app/environment'
export const persistentStore = (key: string, initialValue: T) => {
- const savedValue = browser ? localStorage.getItem(key) : null;
- const data: T = savedValue !== null ? JSON.parse(savedValue) : initialValue;
- const store = writable();
+ const savedValue = browser ? localStorage.getItem(key) : null
+ const data: T = savedValue !== null ? JSON.parse(savedValue) : initialValue
+ const store = writable()
- store.subscribe(value => {
- if (browser) localStorage.setItem(key, JSON.stringify(value));
- });
+ store.subscribe(value => {
+ if (browser) localStorage.setItem(key, JSON.stringify(value))
+ })
- store.set(data);
+ store.set(data)
- return store;
-};
+ return store
+}
diff --git a/app/src/routes/+error.svelte b/app/src/routes/+error.svelte
index c849218..062a73c 100644
--- a/app/src/routes/+error.svelte
+++ b/app/src/routes/+error.svelte
@@ -1,8 +1,8 @@
-
{page.status} {page.error?.message}
-
Go to Home page
+
{page.status} {page.error?.message}
+
Go to Home page
diff --git a/app/src/routes/+layout.svelte b/app/src/routes/+layout.svelte
index d0f6a79..838efb5 100644
--- a/app/src/routes/+layout.svelte
+++ b/app/src/routes/+layout.svelte
@@ -1,129 +1,129 @@
- {page.data.title}
+ {page.data.title}
-
-
-
-
+
+
+
+
-
- {@render children?.()}
-
-
-
-
-
+
+ {@render children?.()}
+
+
+
+
+
-
-
- {#snippet backdrop()}
-
-
- {/snippet}
+
+
+ {#snippet backdrop()}
+
+ {/snippet}
diff --git a/app/src/routes/+layout.ts b/app/src/routes/+layout.ts
index 9f306b8..84f7629 100644
--- a/app/src/routes/+layout.ts
+++ b/app/src/routes/+layout.ts
@@ -2,21 +2,23 @@ export const prerender = true
export const ssr = false
const registerFetchIntercept = async () => {
- const { fetch: originalFetch } = window
- const fileService = (await import('$lib/services/file-service')).default
- window.fetch = async (resource, config) => {
- const url = resource instanceof Request ? resource.url : resource.toString()
- const file = await fileService?.getFile(url)
- return file?.isOk() ? new Response(file.inner) : originalFetch(resource, config)
- }
+ const { fetch: originalFetch } = window
+ const fileService = (await import('$lib/services/file-service')).default
+ window.fetch = async (resource, config) => {
+ const url = resource instanceof Request ? resource.url : resource.toString()
+ const file = await fileService?.getFile(url)
+ return file?.isOk() && file.inner ?
+ new Response(new Uint8Array(file.inner))
+ : originalFetch(resource, config)
+ }
}
export const load = async () => {
- await registerFetchIntercept()
- return {
- title: 'Spot micro controller',
- github: 'runeharlyk/SpotMicroESP32-Leika',
- app_name: 'Spot Micro Controller',
- copyright: '2025 Rune Harlyk'
- }
+ await registerFetchIntercept()
+ return {
+ title: 'Spot micro controller',
+ github: 'runeharlyk/SpotMicroESP32-Leika',
+ app_name: 'Spot Micro Controller',
+ copyright: '2025 Rune Harlyk'
+ }
}
diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte
index f06e9c9..0533f9d 100644
--- a/app/src/routes/+page.svelte
+++ b/app/src/routes/+page.svelte
@@ -1,27 +1,29 @@
-
diff --git a/app/src/routes/connection/+page.svelte b/app/src/routes/connection/+page.svelte
index 71cb700..1d04582 100644
--- a/app/src/routes/connection/+page.svelte
+++ b/app/src/routes/connection/+page.svelte
@@ -1,7 +1,7 @@
-
+
diff --git a/app/src/routes/connection/+page.ts b/app/src/routes/connection/+page.ts
index edd2443..4702682 100644
--- a/app/src/routes/connection/+page.ts
+++ b/app/src/routes/connection/+page.ts
@@ -1,7 +1,7 @@
-import type { PageLoad } from './$types';
+import type { PageLoad } from './$types'
export const load = (async () => {
- return {
- title: 'Connection'
- };
-}) satisfies PageLoad;
+ return {
+ title: 'Connection'
+ }
+}) satisfies PageLoad
diff --git a/app/src/routes/connection/Connection.svelte b/app/src/routes/connection/Connection.svelte
index fb94f88..0c91f88 100644
--- a/app/src/routes/connection/Connection.svelte
+++ b/app/src/routes/connection/Connection.svelte
@@ -1,26 +1,26 @@
- {#snippet icon()}
-
- {/snippet}
- {#snippet title()}
- Connection
- {/snippet}
+ {#snippet icon()}
+
+ {/snippet}
+ {#snippet title()}
+ Connection
+ {/snippet}
-
-
-
-
+
+
+
+
-
+
diff --git a/app/src/routes/controller/+page.svelte b/app/src/routes/controller/+page.svelte
index 0c4bca9..cb5f73d 100644
--- a/app/src/routes/controller/+page.svelte
+++ b/app/src/routes/controller/+page.svelte
@@ -1,31 +1,31 @@
diff --git a/app/src/routes/controller/+page.ts b/app/src/routes/controller/+page.ts
index f8b32d7..0dc72de 100644
--- a/app/src/routes/controller/+page.ts
+++ b/app/src/routes/controller/+page.ts
@@ -1,3 +1,3 @@
export const load = async () => {
- return { title: 'Controller' };
-};
+ return { title: 'Controller' }
+}
diff --git a/app/src/routes/controller/Controls.svelte b/app/src/routes/controller/Controls.svelte
index 0ee77a6..d6c3958 100644
--- a/app/src/routes/controller/Controls.svelte
+++ b/app/src/routes/controller/Controls.svelte
@@ -1,202 +1,210 @@
-
-
-
-
W
+
-
-
A
-
S
-
D
+
+
+ W
+
+
+ A
+ S
+ D
+
+
-
-
-
-
- handleRange(e, 'height')} />
-
-
-
-
- {#each modes as modeValue}
-
- {/each}
-
+
+
+ handleRange(e, 'height')}
+ />
+
+
+
+
+ {#each modes as modeValue}
+
+ {/each}
+
- {#if $mode === ModesEnum.Walk}
-
- {#each Object.values(WalkGaits) as gaitValue}
- {#if typeof gaitValue === 'number'}
-
+ {#if $mode === ModesEnum.Walk}
+
+ {#each Object.values(WalkGaits) as gaitValue}
+ {#if typeof gaitValue === 'number'}
+
+ {/if}
+ {/each}
+
+
+
{/if}
- {/each}
-
-
- {/if}
-
diff --git a/app/src/routes/peripherals/+page.ts b/app/src/routes/peripherals/+page.ts
index c637df9..ac6fbc7 100644
--- a/app/src/routes/peripherals/+page.ts
+++ b/app/src/routes/peripherals/+page.ts
@@ -1,7 +1,7 @@
-import type { PageLoad } from './$types';
-import { goto } from '$app/navigation';
+import type { PageLoad } from './$types'
+import { goto } from '$app/navigation'
export const load = (async () => {
- goto('/');
- return;
-}) satisfies PageLoad;
+ goto('/')
+ return
+}) satisfies PageLoad
diff --git a/app/src/routes/peripherals/camera/+page.svelte b/app/src/routes/peripherals/camera/+page.svelte
index b68f9df..1a9f029 100644
--- a/app/src/routes/peripherals/camera/+page.svelte
+++ b/app/src/routes/peripherals/camera/+page.svelte
@@ -1,7 +1,7 @@
-
+
diff --git a/app/src/routes/peripherals/camera/+page.ts b/app/src/routes/peripherals/camera/+page.ts
index ec75227..679e2a0 100644
--- a/app/src/routes/peripherals/camera/+page.ts
+++ b/app/src/routes/peripherals/camera/+page.ts
@@ -1,7 +1,7 @@
-import type { PageLoad } from './$types';
+import type { PageLoad } from './$types'
export const load = (async () => {
- return {
- title: 'Camera'
- };
-}) satisfies PageLoad;
+ return {
+ title: 'Camera'
+ }
+}) satisfies PageLoad
diff --git a/app/src/routes/peripherals/camera/Camera.svelte b/app/src/routes/peripherals/camera/Camera.svelte
index 5a76859..d2f9a07 100644
--- a/app/src/routes/peripherals/camera/Camera.svelte
+++ b/app/src/routes/peripherals/camera/Camera.svelte
@@ -1,17 +1,17 @@
{#snippet icon()}
-
- {/snippet}
+
+ {/snippet}
{#snippet title()}
- Camera
- {/snippet}
+ Camera
+ {/snippet}
-
\ No newline at end of file
+
diff --git a/app/src/routes/peripherals/camera/CameraSetting.svelte b/app/src/routes/peripherals/camera/CameraSetting.svelte
index fb7894b..add9c0b 100644
--- a/app/src/routes/peripherals/camera/CameraSetting.svelte
+++ b/app/src/routes/peripherals/camera/CameraSetting.svelte
@@ -1,13 +1,13 @@
-
+
diff --git a/app/src/routes/peripherals/i2c/+page.ts b/app/src/routes/peripherals/i2c/+page.ts
index 643baf9..81887a6 100644
--- a/app/src/routes/peripherals/i2c/+page.ts
+++ b/app/src/routes/peripherals/i2c/+page.ts
@@ -1,7 +1,7 @@
import type { PageLoad } from './$types'
export const load = (async () => {
- return {
- title: 'I2C'
- }
+ return {
+ title: 'I2C'
+ }
}) satisfies PageLoad
diff --git a/app/src/routes/peripherals/i2c/i2c.svelte b/app/src/routes/peripherals/i2c/i2c.svelte
index 18dcef2..0c0ea23 100644
--- a/app/src/routes/peripherals/i2c/i2c.svelte
+++ b/app/src/routes/peripherals/i2c/i2c.svelte
@@ -1,79 +1,79 @@
- {#snippet icon()}
-
- {/snippet}
- {#snippet title()}
- I2C
- {/snippet}
- {#snippet right()}
-
- {/snippet}
+ {#snippet icon()}
+
+ {/snippet}
+ {#snippet title()}
+ I2C
+ {/snippet}
+ {#snippet right()}
+
+ {/snippet}
-
+
-
- {#if active_devices.length === 0}
-
No I2C devices found
- {:else}
- {#each active_devices as device}
-
[{device.address.toString(16)}] {device.part_number} - {device.name}
- {/each}
- {/if}
-
+
+ {#if active_devices.length === 0}
+
No I2C devices found
+ {:else}
+ {#each active_devices as device}
+
[{device.address.toString(16)}] {device.part_number} - {device.name}
+ {/each}
+ {/if}
+
diff --git a/app/src/routes/peripherals/i2c/i2cSetting.svelte b/app/src/routes/peripherals/i2c/i2cSetting.svelte
index 1e5f555..8b6bd01 100644
--- a/app/src/routes/peripherals/i2c/i2cSetting.svelte
+++ b/app/src/routes/peripherals/i2c/i2cSetting.svelte
@@ -1,99 +1,107 @@
{#if settings}
-
-
-
Configuration
-
-
{/if}
diff --git a/app/src/routes/peripherals/imu/+page.svelte b/app/src/routes/peripherals/imu/+page.svelte
index d8e667b..8ac2ffa 100644
--- a/app/src/routes/peripherals/imu/+page.svelte
+++ b/app/src/routes/peripherals/imu/+page.svelte
@@ -1,7 +1,7 @@
-
+
diff --git a/app/src/routes/peripherals/imu/+page.ts b/app/src/routes/peripherals/imu/+page.ts
index ab420c7..e080602 100644
--- a/app/src/routes/peripherals/imu/+page.ts
+++ b/app/src/routes/peripherals/imu/+page.ts
@@ -1,7 +1,7 @@
-import type { PageLoad } from './$types';
+import type { PageLoad } from './$types'
export const load = (async () => {
- return {
- title: 'IMU'
- };
-}) satisfies PageLoad;
+ return {
+ title: 'IMU'
+ }
+}) satisfies PageLoad
diff --git a/app/src/routes/peripherals/imu/imu.svelte b/app/src/routes/peripherals/imu/imu.svelte
index 5e61b0c..8b7be03 100644
--- a/app/src/routes/peripherals/imu/imu.svelte
+++ b/app/src/routes/peripherals/imu/imu.svelte
@@ -1,253 +1,256 @@
- {#snippet icon()}
-
- {/snippet}
- {#snippet title()}
- IMU
- {/snippet}
+ {#snippet icon()}
+
+ {/snippet}
+ {#snippet title()}
+ IMU
+ {/snippet}
- {#if $features.imu}
-
- {/if}
+ {#if $features.imu}
+
+ {/if}
- {#if $features.bmp}
-
-
- {/if}
+ {#if $features.bmp}
+
+
+ {/if}
diff --git a/app/src/routes/peripherals/servo/+page.svelte b/app/src/routes/peripherals/servo/+page.svelte
index b2db5be..210bc84 100644
--- a/app/src/routes/peripherals/servo/+page.svelte
+++ b/app/src/routes/peripherals/servo/+page.svelte
@@ -1,12 +1,12 @@
-
-
+
+
diff --git a/app/src/routes/peripherals/servo/+page.ts b/app/src/routes/peripherals/servo/+page.ts
index 715aed7..cbb416c 100644
--- a/app/src/routes/peripherals/servo/+page.ts
+++ b/app/src/routes/peripherals/servo/+page.ts
@@ -1,7 +1,7 @@
-import type { PageLoad } from './$types';
+import type { PageLoad } from './$types'
export const load = (async () => {
- return {
- title: 'Servo'
- };
-}) satisfies PageLoad;
+ return {
+ title: 'Servo'
+ }
+}) satisfies PageLoad
diff --git a/app/src/routes/peripherals/servo/ServoTable.svelte b/app/src/routes/peripherals/servo/ServoTable.svelte
index 984f363..5018d41 100644
--- a/app/src/routes/peripherals/servo/ServoTable.svelte
+++ b/app/src/routes/peripherals/servo/ServoTable.svelte
@@ -1,113 +1,117 @@
-
+
diff --git a/app/src/routes/peripherals/servo/servos.svelte b/app/src/routes/peripherals/servo/servos.svelte
index 61b2fd1..2b8a1b2 100644
--- a/app/src/routes/peripherals/servo/servos.svelte
+++ b/app/src/routes/peripherals/servo/servos.svelte
@@ -1,64 +1,66 @@
-
General servo configuration
- Servo
- {pwm}
+ General servo configuration
+ Servo
+ {pwm}
+ type="range"
+ min="80"
+ max="600"
+ bind:value={pwm}
+ oninput={updatePWM}
+ class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+/>
-
General servo configuration
-
-
-
-
-
-
-
-
-
-
-
-
+ General servo configuration
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/routes/settings/+page.svelte b/app/src/routes/settings/+page.svelte
index 08f26f2..1e171a8 100644
--- a/app/src/routes/settings/+page.svelte
+++ b/app/src/routes/settings/+page.svelte
@@ -37,20 +37,20 @@
Settings
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/app/src/routes/system/+page.ts b/app/src/routes/system/+page.ts
index c637df9..ac6fbc7 100644
--- a/app/src/routes/system/+page.ts
+++ b/app/src/routes/system/+page.ts
@@ -1,7 +1,7 @@
-import type { PageLoad } from './$types';
-import { goto } from '$app/navigation';
+import type { PageLoad } from './$types'
+import { goto } from '$app/navigation'
export const load = (async () => {
- goto('/');
- return;
-}) satisfies PageLoad;
+ goto('/')
+ return
+}) satisfies PageLoad
diff --git a/app/src/routes/system/filesystem/+page.svelte b/app/src/routes/system/filesystem/+page.svelte
index e21bde9..de5e033 100644
--- a/app/src/routes/system/filesystem/+page.svelte
+++ b/app/src/routes/system/filesystem/+page.svelte
@@ -1,7 +1,7 @@
-
+
diff --git a/app/src/routes/system/filesystem/+page.ts b/app/src/routes/system/filesystem/+page.ts
index f976c4e..5ce183c 100644
--- a/app/src/routes/system/filesystem/+page.ts
+++ b/app/src/routes/system/filesystem/+page.ts
@@ -1,5 +1,5 @@
import type { PageLoad } from './$types'
export const load = (async () => {
- return { title: 'File System' }
+ return { title: 'File System' }
}) satisfies PageLoad
diff --git a/app/src/routes/system/filesystem/File.svelte b/app/src/routes/system/filesystem/File.svelte
index d079510..67c5f76 100644
--- a/app/src/routes/system/filesystem/File.svelte
+++ b/app/src/routes/system/filesystem/File.svelte
@@ -1,24 +1,25 @@
-
+
-
+
diff --git a/app/src/routes/system/filesystem/FileSystem.svelte b/app/src/routes/system/filesystem/FileSystem.svelte
index 4fbddc7..a57bc1e 100644
--- a/app/src/routes/system/filesystem/FileSystem.svelte
+++ b/app/src/routes/system/filesystem/FileSystem.svelte
@@ -1,97 +1,97 @@
@@ -100,73 +100,84 @@
-
File System
-
-
-
-
+
File System
+
+
+
+
-
-
- {#await getFiles()}
-
- {:then files}
-
- {/await}
-
+
+
+ {#await getFiles()}
+
+ {:then files}
+
+ {/await}
+
-
-
- {#if filename}
-
-
{filename}
-
- {#if isEditing}
-
-
- {:else}
-
-
- {/if}
-
-
+
+
+ {#if filename}
+
+
{filename}
+
+ {#if isEditing}
+
+
+ {:else}
+
+
+ {/if}
+
+
- {#await getContent(filename)}
-
- {:then _}
- {#if isEditing}
-
+ {#await getContent(filename)}
+
+ {:then _}
+ {#if isEditing}
+
+ {:else}
+
{content}
+ {/if}
+ {/await}
{:else}
-
{content}
+
Select a file to view its contents
{/if}
- {/await}
- {:else}
-
Select a file to view its contents
- {/if}
-
+
diff --git a/app/src/routes/system/filesystem/Folder.svelte b/app/src/routes/system/filesystem/Folder.svelte
index 084f296..1917d9c 100644
--- a/app/src/routes/system/filesystem/Folder.svelte
+++ b/app/src/routes/system/filesystem/Folder.svelte
@@ -1,44 +1,44 @@
-
+
- {#if expanded}
-
- {#each Object.entries(files) as [itemName, content]}
- -
- {#if typeof content === 'object'}
-
- {:else}
-
- {/if}
-
- {/each}
-
- {/if}
+ {#if expanded}
+
+ {#each Object.entries(files) as [itemName, content]}
+ -
+ {#if typeof content === 'object'}
+
+ {:else}
+
+ {/if}
+
+ {/each}
+
+ {/if}
diff --git a/app/src/routes/system/filesystem/NewFileDialog.svelte b/app/src/routes/system/filesystem/NewFileDialog.svelte
index 4695a2b..dd50ddb 100644
--- a/app/src/routes/system/filesystem/NewFileDialog.svelte
+++ b/app/src/routes/system/filesystem/NewFileDialog.svelte
@@ -1,44 +1,50 @@
{#if isOpen}
-
-
Create New File
-
-
-
-
-
-
-
+ role="dialog"
+ class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center"
+ transition:fly={{ y: 50 }}
+ use:exitBeforeEnter
+ use:focusTrap
+ >
+
+
Create New File
+
+
+
+
+
+
+
+
-
{/if}
diff --git a/app/src/routes/system/filesystem/NewFolderDialog.svelte b/app/src/routes/system/filesystem/NewFolderDialog.svelte
index 92d0a75..f735e08 100644
--- a/app/src/routes/system/filesystem/NewFolderDialog.svelte
+++ b/app/src/routes/system/filesystem/NewFolderDialog.svelte
@@ -1,44 +1,50 @@
{#if isOpen}
-
-
Create New Folder
-
-
-
-
-
-
-
+ role="dialog"
+ class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center"
+ transition:fly={{ y: 50 }}
+ use:exitBeforeEnter
+ use:focusTrap
+ >
+
+
Create New Folder
+
+
+
+
+
+
+
+
-
{/if}
diff --git a/app/src/routes/system/metrics/+page.svelte b/app/src/routes/system/metrics/+page.svelte
index 464a328..6d8c97c 100644
--- a/app/src/routes/system/metrics/+page.svelte
+++ b/app/src/routes/system/metrics/+page.svelte
@@ -1,7 +1,7 @@
-
+
diff --git a/app/src/routes/system/metrics/+page.ts b/app/src/routes/system/metrics/+page.ts
index a9b305d..67bf081 100644
--- a/app/src/routes/system/metrics/+page.ts
+++ b/app/src/routes/system/metrics/+page.ts
@@ -1,5 +1,5 @@
-import type { PageLoad } from './$types';
+import type { PageLoad } from './$types'
export const load = (async () => {
- return { title: 'System Metrics' };
-}) satisfies PageLoad;
+ return { title: 'System Metrics' }
+}) satisfies PageLoad
diff --git a/app/src/routes/system/metrics/SystemMetrics.svelte b/app/src/routes/system/metrics/SystemMetrics.svelte
index 2beeefb..58cdca4 100644
--- a/app/src/routes/system/metrics/SystemMetrics.svelte
+++ b/app/src/routes/system/metrics/SystemMetrics.svelte
@@ -1,271 +1,274 @@
- {#snippet icon()}
-
- {/snippet}
- {#snippet title()}
- System Metrics
- {/snippet}
+ {#snippet icon()}
+
+ {/snippet}
+ {#snippet title()}
+ System Metrics
+ {/snippet}
-
-
-
-
-
-
diff --git a/app/src/routes/system/status/+page.svelte b/app/src/routes/system/status/+page.svelte
index e60f77c..58697ee 100644
--- a/app/src/routes/system/status/+page.svelte
+++ b/app/src/routes/system/status/+page.svelte
@@ -1,7 +1,7 @@
-
+
diff --git a/app/src/routes/system/status/+page.ts b/app/src/routes/system/status/+page.ts
index 7a58e83..696b6af 100644
--- a/app/src/routes/system/status/+page.ts
+++ b/app/src/routes/system/status/+page.ts
@@ -1,5 +1,5 @@
import type { PageLoad } from './$types'
export const load = (async () => {
- return { title: 'System Status' }
+ return { title: 'System Status' }
}) satisfies PageLoad
diff --git a/app/src/routes/system/status/ActionButton.svelte b/app/src/routes/system/status/ActionButton.svelte
index b032f6e..9e45c15 100644
--- a/app/src/routes/system/status/ActionButton.svelte
+++ b/app/src/routes/system/status/ActionButton.svelte
@@ -1,10 +1,10 @@
diff --git a/app/src/routes/system/status/SystemStatus.svelte b/app/src/routes/system/status/SystemStatus.svelte
index 3b0051d..b95e2d1 100644
--- a/app/src/routes/system/status/SystemStatus.svelte
+++ b/app/src/routes/system/status/SystemStatus.svelte
@@ -1,252 +1,266 @@
- {#snippet icon()}
-
- {/snippet}
- {#snippet title()}
- System Status
- {/snippet}
+ {#snippet icon()}
+
+ {/snippet}
+ {#snippet title()}
+ System Status
+ {/snippet}
-
- {#await getSystemStatus()}
-
- {:then}
- {#if systemInformation}
-
-
+
+ {#await getSystemStatus()}
+
+ {:then}
+ {#if systemInformation}
+
+
-
+
-
+
-
+
-
+
-
+
-
+ (systemInformation.free_sketch_space - systemInformation.sketch_size) / 1000000
+ } MB free)`}
+ />
-
+
-
+
-
+
-
+
-
-
- {/if}
- {/await}
-
+
+
+ {/if}
+ {/await}
+
-
- {#each actionButtons as button}
- {#if button.condition === undefined || button.condition()}
-
- {/if}
- {/each}
-
+
+ {#each actionButtons as button}
+ {#if button.condition === undefined || button.condition()}
+
+ {/if}
+ {/each}
+
diff --git a/app/src/routes/system/update/+page.svelte b/app/src/routes/system/update/+page.svelte
index a02bf71..8662469 100644
--- a/app/src/routes/system/update/+page.svelte
+++ b/app/src/routes/system/update/+page.svelte
@@ -1,9 +1,9 @@
diff --git a/app/src/routes/system/update/+page.ts b/app/src/routes/system/update/+page.ts
index 655fd11..8916777 100644
--- a/app/src/routes/system/update/+page.ts
+++ b/app/src/routes/system/update/+page.ts
@@ -1,5 +1,5 @@
-import type { PageLoad } from './$types';
+import type { PageLoad } from './$types'
export const load = (async () => {
- return { title: 'Firmware Update' };
-}) satisfies PageLoad;
+ return { title: 'Firmware Update' }
+}) satisfies PageLoad
diff --git a/app/src/routes/system/update/GithubFirmwareManager.svelte b/app/src/routes/system/update/GithubFirmwareManager.svelte
index 1e295be..7096b30 100644
--- a/app/src/routes/system/update/GithubFirmwareManager.svelte
+++ b/app/src/routes/system/update/GithubFirmwareManager.svelte
@@ -1,154 +1,165 @@
- {#snippet icon()}
-
- {/snippet}
- {#snippet title()}
- Github Firmware Manager
- {/snippet}
- {#await getGithubAPI()}
-
- {:then githubReleases}
-
-
-
-
-
- | Release |
- Release Date |
- Experimental |
- Install |
-
-
-
- {#each githubReleases as release}
-
- |
- {release.name} |
-
-
- {new Intl.DateTimeFormat('en-GB', {
- dateStyle: 'medium',
- }).format(new Date(release.published_at))}
-
- |
-
- {#if release.prerelease}
-
- {/if}
- |
-
- {#if compareVersions($features.firmware_version as string, release.tag_name) != 0}
-
- {/if}
- |
-
- {/each}
-
-
-
-
- {:catch error}
-
-
- Please connect to a network with internet access to perform a firmware update.
-
- {/await}
+ {#snippet icon()}
+
+ {/snippet}
+ {#snippet title()}
+ Github Firmware Manager
+ {/snippet}
+ {#await getGithubAPI()}
+
+ {:then githubReleases}
+
+
+
+
+
+ | Release |
+ Release Date |
+ Experimental |
+ Install |
+
+
+
+ {#each githubReleases as release}
+
+ |
+ {release.name} |
+
+
+ {new Intl.DateTimeFormat('en-GB', {
+ dateStyle: 'medium'
+ }).format(new Date(release.published_at))}
+
+ |
+
+ {#if release.prerelease}
+
+ {/if}
+ |
+
+ {#if compareVersions($features.firmware_version as string, release.tag_name) != 0}
+
+ {/if}
+ |
+
+ {/each}
+
+
+
+
+ {:catch error}
+
+
+ Please connect to a network with internet access to perform a firmware update.
+
+ {/await}
diff --git a/app/src/routes/system/update/UploadFirmware.svelte b/app/src/routes/system/update/UploadFirmware.svelte
index c86ff78..2cb6491 100644
--- a/app/src/routes/system/update/UploadFirmware.svelte
+++ b/app/src/routes/system/update/UploadFirmware.svelte
@@ -1,56 +1,57 @@
- {#snippet icon()}
-
- {/snippet}
- {#snippet title()}
- Upload Firmware
- {/snippet}
-
-
- Uploading a new firmware (.bin) file will replace the existing firmware. You may upload a
- (.md5) file first to verify the uploaded firmware.
-
-
+ {#snippet icon()}
+
+ {/snippet}
+ {#snippet title()}
+ Upload Firmware
+ {/snippet}
+
+
+ Uploading a new firmware (.bin) file will replace the existing firmware. You may upload
+ a (.md5) file first to verify the uploaded firmware.
+
+
-
+
diff --git a/app/src/routes/wifi/+page.ts b/app/src/routes/wifi/+page.ts
index c637df9..ac6fbc7 100644
--- a/app/src/routes/wifi/+page.ts
+++ b/app/src/routes/wifi/+page.ts
@@ -1,7 +1,7 @@
-import type { PageLoad } from './$types';
-import { goto } from '$app/navigation';
+import type { PageLoad } from './$types'
+import { goto } from '$app/navigation'
export const load = (async () => {
- goto('/');
- return;
-}) satisfies PageLoad;
+ goto('/')
+ return
+}) satisfies PageLoad
diff --git a/app/src/routes/wifi/ap/+page.svelte b/app/src/routes/wifi/ap/+page.svelte
index 57fb2e6..d1c9216 100644
--- a/app/src/routes/wifi/ap/+page.svelte
+++ b/app/src/routes/wifi/ap/+page.svelte
@@ -1,7 +1,7 @@
diff --git a/app/src/routes/wifi/ap/+page.ts b/app/src/routes/wifi/ap/+page.ts
index 26cb881..236e9a6 100644
--- a/app/src/routes/wifi/ap/+page.ts
+++ b/app/src/routes/wifi/ap/+page.ts
@@ -1,7 +1,7 @@
-import type { PageLoad } from './$types';
+import type { PageLoad } from './$types'
export const load = (async () => {
- return {
- title: 'Access Point'
- };
-}) satisfies PageLoad;
+ return {
+ title: 'Access Point'
+ }
+}) satisfies PageLoad
diff --git a/app/src/routes/wifi/ap/Accesspoint.svelte b/app/src/routes/wifi/ap/Accesspoint.svelte
index 2317fbe..7c66e54 100644
--- a/app/src/routes/wifi/ap/Accesspoint.svelte
+++ b/app/src/routes/wifi/ap/Accesspoint.svelte
@@ -1,365 +1,401 @@
- {#snippet icon()}
-
- {/snippet}
- {#snippet title()}
- Access Point
- {/snippet}
-
- {#await getAPStatus()}
-
- {:then}
- {#if apStatus}
-
-
+ {#snippet icon()}
+
+ {/snippet}
+ {#snippet title()}
+
Access Point
+ {/snippet}
+
+ {#await getAPStatus()}
+
+ {:then}
+ {#if apStatus}
+
+
-
+
-
+
-
-
- {/if}
- {/await}
-
-
-
-
- Change AP Settings
+
+
+ {/if}
+ {/await}
- {#await getAPSettings()}
-
- {:then}
- {#if apSettings}
+
+
-
+ class="min-h-16 flex w-full items-center justify-between space-x-3 p-0 text-xl font-medium"
+ >
+ Change AP Settings
- {/if}
- {/await}
-
+ {#await getAPSettings()}
+
+ {:then}
+ {#if apSettings}
+
+ {/if}
+ {/await}
+
diff --git a/app/src/routes/wifi/mdns/+page.svelte b/app/src/routes/wifi/mdns/+page.svelte
index b0db4a6..cf25238 100644
--- a/app/src/routes/wifi/mdns/+page.svelte
+++ b/app/src/routes/wifi/mdns/+page.svelte
@@ -1,7 +1,7 @@
-
+
diff --git a/app/src/routes/wifi/mdns/MDNS.svelte b/app/src/routes/wifi/mdns/MDNS.svelte
index d5d92d9..217ee43 100644
--- a/app/src/routes/wifi/mdns/MDNS.svelte
+++ b/app/src/routes/wifi/mdns/MDNS.svelte
@@ -1,100 +1,105 @@
- {#snippet icon()}
-
- {/snippet}
- {#snippet title()}
- MDNS
- {/snippet}
- {#snippet right()}
-
- {/snippet}
-
- {#if mdnsStatus}
-
-
+ {#snippet icon()}
+
+ {/snippet}
+ {#snippet title()}
+
MDNS
+ {/snippet}
+ {#snippet right()}
+
+ {/snippet}
+
+ {#if mdnsStatus}
+
+
-
+
-
+
-
-
-
- |
- Name |
- Ip address |
- Port |
-
-
-
- {#each services as service}
-
- |
- {service.name} |
- {service.ip} |
- {service.port} |
-
- {/each}
-
-
-
- {/if}
-
+
+
+
+ |
+ Name |
+ Ip address |
+ Port |
+
+
+
+ {#each services as service}
+
+ |
+ {service.name} |
+ {service.ip} |
+ {service.port} |
+
+ {/each}
+
+
+
+ {/if}
+
diff --git a/app/src/routes/wifi/sta/+page.svelte b/app/src/routes/wifi/sta/+page.svelte
index c874b6e..95b0c51 100644
--- a/app/src/routes/wifi/sta/+page.svelte
+++ b/app/src/routes/wifi/sta/+page.svelte
@@ -1,7 +1,7 @@
-
+
diff --git a/app/src/routes/wifi/sta/+page.ts b/app/src/routes/wifi/sta/+page.ts
index 18a651f..859410d 100644
--- a/app/src/routes/wifi/sta/+page.ts
+++ b/app/src/routes/wifi/sta/+page.ts
@@ -1,7 +1,7 @@
-import type { PageLoad } from './$types';
+import type { PageLoad } from './$types'
export const load = (async () => {
- return {
- title: 'WiFi Station'
- };
-}) satisfies PageLoad;
+ return {
+ title: 'WiFi Station'
+ }
+}) satisfies PageLoad
diff --git a/app/src/routes/wifi/sta/Scan.svelte b/app/src/routes/wifi/sta/Scan.svelte
index df582a9..ddb9ba2 100644
--- a/app/src/routes/wifi/sta/Scan.svelte
+++ b/app/src/routes/wifi/sta/Scan.svelte
@@ -1,131 +1,141 @@
{#if isOpen}
-
-
Scan Networks
-
-
- {#if scanActive}
- {:else}
-
-
{/if}
diff --git a/app/src/routes/wifi/sta/Wifi.svelte b/app/src/routes/wifi/sta/Wifi.svelte
index 7292c0a..f7fabf4 100644
--- a/app/src/routes/wifi/sta/Wifi.svelte
+++ b/app/src/routes/wifi/sta/Wifi.svelte
@@ -1,613 +1,709 @@
- {#snippet icon()}
-
- {/snippet}
- {#snippet title()}
- WiFi Connection
- {/snippet}
-
- {#await getWifiStatus()}
-
- {:then}
- {#if wifiStatus}
-
-
-
- {#if wifiStatus.status === 3}
-
-
-
-
-
-
-
- {/if}
-
-
-
- {#if showWifiDetails}
-
-
-
-
-
-
-
-
-
-
-
- {/if}
- {/if}
- {/await}
-
-
-
-
- Saved Networks
-
- {#await getWifiSettings()}
-
- {:then}
- {#if wifiSettings}
-
-
-
-
-
-
- {#snippet children({ index }: { index: number })}
-
-
-
-
-
-
- {/snippet}
-
-
-
-
-
-
diff --git a/app/static/manifest.json b/app/static/manifest.json
index 98d7cc4..f9138da 100644
--- a/app/static/manifest.json
+++ b/app/static/manifest.json
@@ -1,15 +1,15 @@
{
- "short_name": "Quadruped Controller",
- "name": "Quadruped Controller",
- "icons": [
- {
- "src": "logo512.png",
- "type": "image/png",
- "sizes": "512x512"
- }
- ],
- "start_url": ".",
- "display": "standalone",
- "theme_color": "#000000",
- "background_color": "#ffffff"
+ "short_name": "Quadruped Controller",
+ "name": "Quadruped Controller",
+ "icons": [
+ {
+ "src": "logo512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
}
diff --git a/app/static/sw.js b/app/static/sw.js
index 3171814..d78b192 100644
--- a/app/static/sw.js
+++ b/app/static/sw.js
@@ -1,13 +1,13 @@
-const CACHE_NAME = 'v1';
-const urlsToCache = ['/', '/index.html', '/stl.zip'];
+const CACHE_NAME = 'v1'
+const urlsToCache = ['/', '/index.html', '/stl.zip']
-self.addEventListener('install', (event) => {
- event.waitUntil(
- caches.open(CACHE_NAME).then((cache) => {
- return cache.addAll(urlsToCache);
- })
- );
-});
+self.addEventListener('install', event => {
+ event.waitUntil(
+ caches.open(CACHE_NAME).then(cache => {
+ return cache.addAll(urlsToCache)
+ })
+ )
+})
// self.addEventListener('fetch', (event) => {
// event.respondWith(
@@ -29,17 +29,17 @@ self.addEventListener('install', (event) => {
// );
// });
-self.addEventListener('activate', (event) => {
- var cacheWhitelist = ['v1'];
- event.waitUntil(
- caches.keys().then((cacheNames) => {
- return Promise.all(
- cacheNames.map((cacheName) => {
- if (cacheWhitelist.indexOf(cacheName) === -1) {
- return caches.delete(cacheName);
- }
- })
- );
- })
- );
-});
+self.addEventListener('activate', event => {
+ var cacheWhitelist = ['v1']
+ event.waitUntil(
+ caches.keys().then(cacheNames => {
+ return Promise.all(
+ cacheNames.map(cacheName => {
+ if (cacheWhitelist.indexOf(cacheName) === -1) {
+ return caches.delete(cacheName)
+ }
+ })
+ )
+ })
+ )
+})
diff --git a/app/svelte.config.js b/app/svelte.config.js
index d748139..42ad879 100644
--- a/app/svelte.config.js
+++ b/app/svelte.config.js
@@ -5,20 +5,20 @@ const basePath = process.env.BASE_PATH ?? ''
/** @type {import('@sveltejs/kit').Config} */
const config = {
- preprocess: vitePreprocess(),
+ preprocess: vitePreprocess(),
- kit: {
- adapter: adapter({
- pages: 'build',
- assets: 'build',
- fallback: 'index.html',
- precompress: false,
- strict: true
- }),
- paths: {
- base: basePath
+ kit: {
+ adapter: adapter({
+ pages: 'build',
+ assets: 'build',
+ fallback: 'index.html',
+ precompress: false,
+ strict: true
+ }),
+ paths: {
+ base: basePath
+ }
}
- }
}
export default config
diff --git a/app/vite-plugin-littlefs.ts b/app/vite-plugin-littlefs.ts
index 9bfb12b..b445bdf 100644
--- a/app/vite-plugin-littlefs.ts
+++ b/app/vite-plugin-littlefs.ts
@@ -1,49 +1,49 @@
-import type { Plugin } from 'vite';
+import type { Plugin } from 'vite'
export default function viteLittleFS(): Plugin[] {
- return [
- {
- name: 'vite-plugin-littlefs',
- enforce: 'post',
- apply: 'build',
+ return [
+ {
+ name: 'vite-plugin-littlefs',
+ enforce: 'post',
+ apply: 'build',
- async config(config) {
- const output = config.build?.rollupOptions?.output;
+ async config(config) {
+ const output = config.build?.rollupOptions?.output
- if (!output || !config.build?.rollupOptions) {
- return;
+ if (!output || !config.build?.rollupOptions) {
+ return
+ }
+
+ const outputOptions = Array.isArray(output) ? output[0] : output
+
+ if (!outputOptions) {
+ return
+ }
+
+ const { assetFileNames, chunkFileNames, entryFileNames } = outputOptions
+
+ if (assetFileNames && typeof assetFileNames === 'string') {
+ config.build.rollupOptions.output = {
+ ...outputOptions,
+ assetFileNames: assetFileNames.replace('.[hash]', '')
+ }
+ }
+
+ if (
+ chunkFileNames &&
+ typeof chunkFileNames === 'string' &&
+ chunkFileNames.includes('hash')
+ ) {
+ config.build.rollupOptions.output = {
+ ...config.build.rollupOptions.output,
+ chunkFileNames: chunkFileNames.replace('.[hash]', ''),
+ ...(entryFileNames &&
+ typeof entryFileNames === 'string' && {
+ entryFileNames: entryFileNames.replace('.[hash]', '')
+ })
+ }
+ }
+ }
}
-
- const outputOptions = Array.isArray(output) ? output[0] : output;
-
- if (!outputOptions) {
- return;
- }
-
- const { assetFileNames, chunkFileNames, entryFileNames } = outputOptions;
-
- if (assetFileNames && typeof assetFileNames === 'string') {
- config.build.rollupOptions.output = {
- ...outputOptions,
- assetFileNames: assetFileNames.replace('.[hash]', ''),
- };
- }
-
- if (
- chunkFileNames &&
- typeof chunkFileNames === 'string' &&
- chunkFileNames.includes('hash')
- ) {
- config.build.rollupOptions.output = {
- ...config.build.rollupOptions.output,
- chunkFileNames: chunkFileNames.replace('.[hash]', ''),
- ...(entryFileNames &&
- typeof entryFileNames === 'string' && {
- entryFileNames: entryFileNames.replace('.[hash]', ''),
- }),
- };
- }
- },
- },
- ];
+ ]
}
diff --git a/app/vite.config.ts b/app/vite.config.ts
index d1c77b9..33284b8 100644
--- a/app/vite.config.ts
+++ b/app/vite.config.ts
@@ -8,23 +8,23 @@ import tailwindcss from '@tailwindcss/vite'
const basePath = process.env.BASE_PATH ?? ''
export default defineConfig({
- base: basePath,
- plugins: [
- tailwindcss(),
- sveltekit(),
- Icons({
- compiler: 'svelte'
- }),
- viteLittleFS(),
- EnvCaster()
- ],
- server: {
- proxy: {
- '/api': {
- target: 'http://spot-micro.local/',
- changeOrigin: true,
- ws: true
- }
+ base: basePath,
+ plugins: [
+ tailwindcss(),
+ sveltekit(),
+ Icons({
+ compiler: 'svelte'
+ }),
+ viteLittleFS(),
+ EnvCaster()
+ ],
+ server: {
+ proxy: {
+ '/api': {
+ target: 'http://spot-micro.local/',
+ changeOrigin: true,
+ ws: true
+ }
+ }
}
- }
})