Deletes old project

This commit is contained in:
Rune Harlyk
2024-04-25 21:57:34 +02:00
committed by Rune Harlyk
parent 0b4fe8a0ef
commit 027d5eebc7
189 changed files with 1341 additions and 7239 deletions
View File
-3
View File
@@ -1,3 +0,0 @@
VITE_API_URL="leika.local"
VITE_SOCKET_URL="leika.local"
VITE_EMBEDDED_BUILD=true
-3
View File
@@ -1,3 +0,0 @@
VITE_API_URL="hostname"
VITE_SOCKET_URL="hostname:2096"
VITE_EMBEDDED_BUILD=true
-3
View File
@@ -1,3 +0,0 @@
VITE_API_URL="hostname"
VITE_SOCKET_URL="hostname:2096"
VITE_EMBEDDED_BUILD=false
-3
View File
@@ -1,3 +0,0 @@
VITE_API_URL="leika.local"
VITE_SOCKET_URL="leika.local"
VITE_EMBEDDED_BUILD=false
+1 -1
View File
@@ -10,4 +10,4 @@ node_modules
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
yarn.lock
+20 -9
View File
@@ -1,20 +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',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
}
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
};
-15
View File
@@ -1,15 +0,0 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"overrides": [],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"rules": {}
}
+9 -23
View File
@@ -1,24 +1,10 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
View File
+1 -10
View File
@@ -1,13 +1,4 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
yarn.lock
-1
View File
@@ -4,6 +4,5 @@
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}
+37 -2
View File
@@ -1,3 +1,38 @@
# Controller App
# create-svelte
This is the controller for my spot micro
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
-16
View File
@@ -1,16 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/logo512.png" />
<link rel="manifest" href="/manifest.json">
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"
/>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
View File
+46 -41
View File
@@ -1,55 +1,60 @@
{
"name": "app",
"name": "app2",
"version": "0.0.1",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --mode embedded",
"dev:mock_embedded": "vite --mode mock_embedded",
"dev:mock_web": "vite --mode mock_web",
"build": "vite build --mode embedded",
"build:mock_web": "vite build --mode mock_web",
"build:web": "vite build --mode web",
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"test": "vitest --environment jsdom",
"check": "svelte-check --tsconfig ./tsconfig.json",
"format": "prettier --plugin-search-dir . --write ."
"test": "npm run test:integration && npm 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": {
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@tsconfig/svelte": "^5.0.2",
"@types/three": "^0.160.0",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.20.0",
"autoprefixer": "^10.4.17",
"cross-env": "^7.0.3",
"husky": "^9.0.7",
"jsdom": "^24.0.0",
"lint-staged": "^15.2.0",
"postcss": "^8.4.33",
"prettier": "3.2.4",
"svelte": "^4.2.9",
"svelte-check": "^3.6.3",
"svelte-hero-icons": "^5.0.0",
"tailwindcss": "^3.4.1",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"vite": "^5.0.12",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-singlefile": "^1.0.0",
"vitest": "^1.3.1"
"@iconify-json/mdi": "^1.1.64",
"@iconify-json/tabler": "^1.1.109",
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.5.5",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@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.35.1",
"postcss": "^8.4.38",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"svelte": "^4.2.7",
"svelte-check": "^3.6.0",
"svelte-focus-trap": "^1.2.0",
"tailwindcss": "^3.4.3",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"unplugin-icons": "^0.18.5",
"vite": "^5.0.3",
"vitest": "^1.2.0"
},
"type": "module",
"dependencies": {
"chart.js": "^4.4.2",
"compare-versions": "^6.1.0",
"daisyui": "^4.10.2",
"jwt-decode": "^4.0.0",
"nipplejs": "^0.10.1",
"prettier-plugin-svelte": "^3.2.1",
"svelte-routing": "^2.11.0",
"three": "^0.160.1",
"svelte-dnd-list": "^0.1.8",
"svelte-modals": "^1.3.0",
"three": "^0.162.0",
"urdf-loader": "^0.12.1",
"uzip": "^0.20201231.0",
"xacro-parser": "^0.3.9"
},
"lint-staged": {
"*.js": "eslint --cache --fix",
"*.{js,css,md,ts,svelte}": "prettier --write"
}
}
+949 -895
View File
File diff suppressed because it is too large Load Diff
+3 -4
View File
@@ -1,6 +1,5 @@
import tailwindcss from 'tailwindcss';
import autoprefixer from 'autoprefixer';
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
plugins: [tailwindcss(), autoprefixer()]
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.
-47
View File
@@ -1,47 +0,0 @@
<script lang="ts">
import { Router, Route } from 'svelte-routing';
import { onMount } from 'svelte';
import TopBar from './components/Topbar.svelte';
import socketService from '$lib/services/socket-service';
import Controller from './routes/Controller.svelte';
import { fileService } from '$lib/services';
import Settings from './routes/Settings.svelte';
import { jointNames, model, outControllerData, mode } from '$lib/stores';
import { loadModelAsync, socketLocation } from '$lib/utilities';
import type { Result } from '$lib/utilities/result';
export let url = window.location.pathname;
onMount(async () => {
socketService.connect(socketLocation);
socketService.addPublisher(outControllerData);
socketService.addPublisher(mode, 'mode');
registerFetchIntercept();
const modelRes = await loadModelAsync('/spot_micro.urdf.xacro');
if (modelRes.isOk()) {
const [urdf, JOINT_NAME] = modelRes.inner;
jointNames.set(JOINT_NAME);
model.set(urdf);
} else {
console.error(modelRes.inner, { exception: modelRes.exception });
}
});
const registerFetchIntercept = () => {
const { fetch: originalFetch } = window;
window.fetch = async (resource, config) => {
let url = resource instanceof Request ? resource.url : resource.toString();
let file = await fileService.getFile(url);
return file.isOk() ? new Response(file.inner) : originalFetch(resource, config);
};
};
</script>
<Router {url}>
<TopBar />
<div class="absolute w-full h-full bg-background text-on-background">
<Route path="/" component={Controller} />
<Route path="/settings/*page" component={Settings} />
</div>
</Router>
+13 -15
View File
@@ -1,20 +1,18 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
@tailwind base;
@tailwind components;
@tailwind utilities;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
#nipple_0_0, #nipple_1_1 {
z-index: 10!important;
}
#three-gui-panel {
top: 50px;
right:0px
top: 64px;
right: 0px;
}
@media (max-width: 1023px) {
#three-gui-panel {
top: 48px;
}
}
View File
-112
View File
@@ -1,112 +0,0 @@
<script lang="ts">
import nipplejs from 'nipplejs';
import { onMount } from 'svelte';
import { capitalize, throttler, toInt8 } from '$lib/utilities';
import { input, outControllerData, mode, modes, type Modes } from '$lib/stores';
import type { vector } from '$lib/models';
import Range from './input/Range.svelte';
import Button from './input/Button.svelte';
let throttle = new throttler();
let left: nipplejs.JoystickManager;
let right: nipplejs.JoystickManager;
let throttle_timing = 40;
let data = new Int8Array(7);
onMount(() => {
left = nipplejs.create({
zone: document.getElementById('left') as HTMLElement,
color: 'grey',
dynamicPage: true,
mode: 'static',
restOpacity: 0.3
});
right = nipplejs.create({
zone: document.getElementById('right') as HTMLElement,
color: 'grey',
dynamicPage: true,
mode: 'static',
restOpacity: 0.3
});
left.on('move', (_, data) => handleJoyMove('left', data.vector));
left.on('end', (_, __) => handleJoyMove('left', { x: 0, y: 0 }));
right.on('move', (_, data) => handleJoyMove('right', data.vector));
right.on('end', (_, __) => handleJoyMove('right', { x: 0, y: 0 }));
});
const handleJoyMove = (key: 'left' | 'right', data: vector) => {
input.update((inputData) => {
inputData[key] = data;
return inputData;
});
throttle.throttle(updateData, throttle_timing);
};
const updateData = () => {
data[0] = 0;
data[1] = toInt8($input.left.x, -1, 1);
data[2] = toInt8($input.left.y, -1, 1);
data[3] = toInt8($input.right.x, -1, 1);
data[4] = toInt8($input.right.y, -1, 1);
data[5] = toInt8($input.height, 0, 100);
data[6] = toInt8($input.speed, 0, 100);
outControllerData.set(data);
};
const handleKeyup = (event: KeyboardEvent) => {
const down = event.type === 'keydown';
input.update((data) => {
if (event.key === 'w') data.left.y = down ? -1 : 0;
if (event.key === 'a') data.left.x = down ? -1 : 0;
if (event.key === 's') data.left.y = down ? 1 : 0;
if (event.key === 'd') data.left.x = down ? 1 : 0;
return data;
});
throttle.throttle(updateData, throttle_timing);
};
const handleRange = (event:CustomEvent, key: 'speed' | 'height') => {
const value:number = event.detail
input.update((inputData) => {
inputData[key] = value;
return inputData;
});
throttle.throttle(updateData, throttle_timing);
}
const changeMode = (modeValue: Modes) => {
mode.set(modeValue);
};
</script>
<div class="absolute top-0 left-0 w-screen h-screen">
<div class="absolute top-0 left-0 h-full w-full flex portrait:hidden">
<div id="left" class="flex w-60 items-center justify-end" />
<div class="flex-1" />
<div id="right" class="flex w-60 items-center" />
</div>
<div class="absolute bottom-0 z-10 p-4 gap-4 flex items-end">
{#each modes as modeValue}
<div>
<Button
on:click={() => changeMode(modeValue)}
active={$mode === modeValue}
>
{capitalize(modeValue)}
</Button>
</div>
{/each}
<div>
{#if $mode === 'walk'}
<Range label="Speed" on:value={(e) => handleRange(e, 'speed')}></Range>
{/if}
<Range label="Height" on:value={(e) => handleRange(e, 'height')}></Range>
</div>
</div>
</div>
<svelte:window on:keyup={handleKeyup} on:keydown={handleKeyup} />
-84
View File
@@ -1,84 +0,0 @@
<script lang="ts">
import socketService from '$lib/services/socket-service';
import { Icon, Bars3, XMark, Power, Battery100, Signal, SignalSlash } from 'svelte-hero-icons';
import { emulateModel } from '$lib/stores';
import { Link, useLocation } from 'svelte-routing';
import { isConnected } from '$lib/stores';
const views = ['Virtual environment', 'Robot camera'];
const modes = ['Drive', 'Choreography'];
const location = useLocation();
let selected_view = views[0];
let selected_modes = modes[0];
let settingOpen = window.location.pathname.includes('/settings');
$: emulateModel.set(selected_view === views[0]);
$: settingOpen = $location.pathname.includes('/settings');
const stop = () => {
if ($isConnected) {
socketService.send(JSON.stringify({ type: 'system/stop' }));
}
};
</script>
<div class="topbar absolute left-0 top-0 w-full z-10 flex justify-between bg-zinc-800">
<div class="flex gap-2 p-2">
{#if settingOpen}
<Link to="/">
<Icon src={XMark} size="32" />
</Link>
{:else}
<Link to="/settings">
<Icon src={Bars3} size="32" />
</Link>
{/if}
<select
bind:value={selected_modes}
class="rounded-md outline outline-2 text-zinc-200 outline-zinc-600 bg-zinc-800"
>
{#each modes as mode}
<option>{mode}</option>
{/each}
</select>
<select
bind:value={selected_view}
class="rounded-md outline outline-2 text-zinc-200 outline-zinc-600 bg-zinc-800"
>
{#each views as view}
<option>{view}</option>
{/each}
</select>
</div>
<div class="flex gap-2 p-2">
<button class="action_button bg-zinc-600">
<Icon src={Power} size="24" />
</button>
<button class="action_button"><Icon src={Battery100} size="24" /></button>
<button class="action_button"
><Icon src={$isConnected ? Signal : SignalSlash} size="24" /></button
>
</div>
<div>
<button class="h-full w-20 bg-red-600 text-white" on:click={stop}>STOP</button>
</div>
</div>
<style>
.topbar {
height: 50px;
}
.action_button {
border-radius: 4px;
width: 34px;
height: 34px;
display: flex;
justify-content: center;
align-items: center;
outline: 1px solid #52525b;
}
</style>
-174
View File
@@ -1,174 +0,0 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import { BufferGeometry, CanvasTexture, CircleGeometry, CubicBezierCurve3, Line, LineBasicMaterial, Mesh, MeshBasicMaterial, Vector3, type NormalBufferAttributes } from 'three';
import socketService from '$lib/services/socket-service';
import uzip from 'uzip';
import { model } from '$lib/stores';
import { footColor, isEmbeddedApp, location, toeWorldPositions } from '$lib/utilities';
import { fileService } from '$lib/services';
import { servoAngles, mpu, jointNames } from '$lib/stores';
import SceneBuilder from '$lib/sceneBuilder';
import { lerp, degToRad } from 'three/src/math/MathUtils';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
let sceneManager = new SceneBuilder();
let canvas: HTMLCanvasElement, streamCanvas: HTMLCanvasElement, stream: HTMLImageElement;
let context: CanvasRenderingContext2D, texture: CanvasTexture;
let modelAngles: number[] | Int16Array = new Array(12).fill(0);
let modelTargetAngles: number[] | Int16Array = new Array(12).fill(0);
let feet_trace = new Array(4).fill([]);
let trace_lines: BufferGeometry<NormalBufferAttributes>[] = []
const videoStream = `//${location}/api/stream`;
let showStream = false;
let settings = {
'Trace feet':true,
'Trace points': 30
}
onMount(async () => {
await cacheModelFiles()
await createScene();
if (!isEmbeddedApp) createPanel();
});
onDestroy(() => {
canvas.remove();
});
const createPanel = () => {
const panel = new GUI({width: 310});
panel.close();
panel.domElement.id = 'three-gui-panel';
const visibility = panel.addFolder('Visualization');
visibility.add(settings, 'Trace feet')
visibility.add(settings, 'Trace points', 1, 1000, 1)
}
const cacheModelFiles = async () => {
let data = await fetch('/stl.zip').then((data) => data.arrayBuffer());
var files = uzip.parse(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);
}
};
const updateAngles = (name: string, angle: number) => {
modelTargetAngles[$jointNames.indexOf(name)] = angle * (180 / Math.PI);
socketService.send(
JSON.stringify({
type: 'kinematic/angle',
angle: angle * (180 / Math.PI),
id: $jointNames.indexOf(name)
})
);
};
const createScene = async () => {
sceneManager
.addRenderer({ antialias: true, canvas: canvas, alpha: true })
.addPerspectiveCamera({ x: -0.5, y: 0.5, z: 1 })
.addOrbitControls(8, 30)
.addSky()
.addGroundPlane()
.addGridHelper({ size: 250, divisions: 125 })
.addAmbientLight({ color: 0xffffff, intensity: 0.7 })
.addDirectionalLight({ x: 10, y: 100, z: 10, color: 0xffffff, intensity: 1 })
.addArrowHelper({ origin: { x: 0, y: 0, z: 0 }, direction: { x: 0, y: -2, z: 0 } })
.addFogExp2(0xcccccc, 0.015)
.addModel($model)
.addDragControl(updateAngles)
.handleResize()
.addRenderCb(render)
.startRenderLoop();
addVideoStream();
for (let i = 0; i < 4; i++) {
const geometry = new BufferGeometry();
const material = new LineBasicMaterial({ color: footColor() });
const line = new Line(geometry, material);
trace_lines.push(geometry);
sceneManager.scene.add(line);
}
};
const addVideoStream = () => {
context = streamCanvas.getContext('2d')!;
texture = new CanvasTexture(stream);
const liveStream = new Mesh(
new CircleGeometry(35, 32),
new MeshBasicMaterial({ map: texture })
);
liveStream.position.z = -50;
liveStream.visible = showStream;
sceneManager.scene.add(liveStream);
};
const handleVideoStream = () => {
if (!showStream) return;
context.drawImage(stream, 0, 0);
texture.needsUpdate = true;
};
const renderTraceLines = (foot_positions: Vector3[]) => {
if (!settings['Trace feet']) {
if (!feet_trace.length) return
trace_lines.forEach((line, i) => line.setFromPoints(feet_trace[i].slice(-1)))
feet_trace = new Array(4).fill([])
return
}
trace_lines.forEach((line, i) => {
feet_trace[i].push(foot_positions[i])
feet_trace[i] = feet_trace[i].slice(-settings['Trace points'])
line.setFromPoints(feet_trace[i]);
})
}
const render = () => {
const robot = sceneManager.model;
if (!robot) return;
const toes = toeWorldPositions(robot)
renderTraceLines(toes)
robot.position.y = robot.position.y - Math.min(...toes.map(toe => toe.y));
robot.rotation.z = lerp(robot.rotation.z, degToRad($mpu.heading + 90), 0.1);
modelTargetAngles = $servoAngles;
handleVideoStream();
for (let i = 0; i < $jointNames.length; i++) {
modelAngles[i] = lerp(
(robot.joints[$jointNames[i]].angle as number) * (180 / Math.PI),
modelTargetAngles[i],
0.1
);
robot.joints[$jointNames[i]].setJointValue(degToRad(modelAngles[i]));
}
};
</script>
<svelte:window on:resize={sceneManager.handleResize} />
{#if showStream}
<img
bind:this={stream}
src={videoStream}
class="hidden"
alt="Live stream is down"
crossorigin="anonymous"
/>
{/if}
<canvas bind:this={streamCanvas} class="hidden"></canvas>
<canvas bind:this={canvas} class="absolute"></canvas>
-19
View File
@@ -1,19 +0,0 @@
<script lang="ts">
import { onDestroy } from 'svelte';
import { location } from '$lib/utilities';
let videoStream = `//${location}/api/stream`;
onDestroy(() => {
videoStream = '#';
});
</script>
<div class="w-full h-full">
<img
src={videoStream}
class="absolute object-cover blur-3xl w-full h-full -z-10"
alt="Live stream is down"
/>
<img src={videoStream} class="object-contain w-full h-full" alt="Live stream is down" />
</div>
-11
View File
@@ -1,11 +0,0 @@
<script lang="ts">
export let active = false
</script>
<button
on:click
class={$$restProps.class + ' rounded-md outline outline-2 text-zinc-200 outline-zinc-600 p-2' +
(active ? ' bg-zinc-600' : '')}
>
<slot/>
</button>
-29
View File
@@ -1,29 +0,0 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
export let value = 50;
export let min = 0;
export let max = 100;
export let label = '';
const dispatchValueInput = () => {
dispatch('value', value)
}
</script>
<div class="">
<input
id="range"
type="range"
{min}
{max}
bind:value
on:change
on:input={dispatchValueInput}
class="w-32 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<label for="range" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{label}</label
>
@@ -1,86 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte';
import { jointNames } from '../../lib/stores';
type Servo = {
id: number;
name: string;
minPWM: number;
maxPWM: number;
pwmFor180: number;
};
let servos: Servo[] = [];
onMount(() => {
jointNames.subscribe((data) => {
servos = data.map((name: string, i: number) => {
return {
id: i,
name,
minPWM: 0,
maxPWM: 0,
pwmFor180: 0
};
});
});
});
let selectedServo: number | null = null;
function updateServoValue(index: number, field: keyof Servo, value: number): void {
servos[index] = { ...servos[index], [field]: value };
}
const formatServo = (servo: Servo) => {
const string = servo.name;
const name = string.charAt(0).toUpperCase() + string.split('_').join(' ').slice(1);
return `${servo.id} ${name}`;
};
</script>
<div>
<div class="servo-selector">
<label for="servo-select">Select Servo:</label>
<select id="servo-select" class="bg-zinc-800" bind:value={selectedServo}>
{#each servos as servo}
<option value={servo.id}>{formatServo(servo)}</option>
{/each}
</select>
</div>
{#if selectedServo !== null}
<div class="mt-5">
<h2>Servo {formatServo(servos[selectedServo])} Calibration</h2>
<label for="minPWM">Min PWM:</label>
<input
type="number"
id="minPWM"
class="bg-zinc-800"
value={servos[selectedServo].minPWM}
on:blur={(event) =>
updateServoValue(selectedServo ?? 0, 'minPWM', Number(event.target?.value))}
/>
<label for="maxPWM">Max PWM:</label>
<input
type="number"
id="maxPWM"
class="bg-zinc-800"
value={servos[selectedServo].maxPWM}
on:blur={(event) =>
updateServoValue(selectedServo ?? 0, 'maxPWM', Number(event.target?.value))}
/>
<label for="pwmFor180">PWM for 180°:</label>
<input
type="number"
id="pwmFor180"
class="bg-zinc-800"
value={servos[selectedServo].pwmFor180}
on:blur={(event) =>
updateServoValue(selectedServo ?? 0, 'pwmFor180', Number(event.target?.value))}
/>
</div>
{/if}
</div>
@@ -1,23 +0,0 @@
<script lang="ts">
import { socketService } from '$lib/services';
import { isConnected, settings } from '$lib/stores';
import { onMount } from 'svelte';
onMount(() => {
if ($isConnected) {
const message = JSON.stringify({ type: 'system/settings' });
socketService.send(message);
}
});
</script>
<div class="w-full h-full">
<div>
{#each Object.entries($settings) as entry}
<div class="flex gap-8">
<div class="w-32">{entry[0]}:</div>
<div>{entry[1]}</div>
</div>
{/each}
</div>
</div>
-28
View File
@@ -1,28 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte';
import { humanFileSize } from '$lib/utilities';
import socketService from '$lib/services/socket-service';
import { isConnected, systemInfo } from '$lib/stores';
onMount(() => {
if ($isConnected) {
const message = JSON.stringify({ type: 'system/info' });
socketService.send(message);
}
});
</script>
<div class="w-full h-full">
<div class="w-1/3">
{#each Object.entries($systemInfo ?? {}) as entry}
<div class="flex gap-8">
<div class="w-32">{entry[0]}:</div>
{#if entry[0].includes('Size') || entry[0].includes('Free') || entry[0].includes('Min')}
<div>{humanFileSize(entry[1])}</div>
{:else}
<div>{entry[1]}</div>
{/if}
</div>
{/each}
</div>
</div>
-18
View File
@@ -1,18 +0,0 @@
<script lang="ts">
import socketService from '$lib/services/socket-service';
import { isConnected, logs } from '$lib/stores';
import { onMount } from 'svelte';
onMount(() => {
if ($isConnected) {
const message = JSON.stringify({ type: 'system/logs' });
socketService.send(message);
}
});
</script>
<div class="w-full h-full">
{#each $logs as entry}
<div>{entry}</div>
{/each}
</div>
-35
View File
@@ -1,35 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* @layer base {
:root {
--primary: 98 0 238;
--primary-variant: 55 0 179;
--secondary: 55 0 179;
--secondary-variant: 55 0 179;
--background: 255 255 255;
--surface: 251 251 250;
--error: 176 0 32;
--on-primary: 255 255 255;
--on-secondary: 0 0 0;
--on-background: 0 0 0;
--on-surface: 0 0 0;
--on-error: 255 255 255;
}
:root[class~="dark"] {
--primary: 98 0 238;
--primary-variant: 55 0 179;
--secondary: 55 0 179;
--secondary-variant: 55 0 179;
--background: 30 30 30;
--surface: 36 36 36;
--error: 176 0 32;
--on-primary: 255 255 255;
--on-secondary: 255 255 255;
--on-background: 255 255 255;
--on-surface: 255 255 255;
--on-error: 255 255 255;
}
} */

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

+127 -7
View File
@@ -9,14 +9,134 @@ export interface ControllerInput {
export type angles = number[] | Int16Array;
export type AnglesData = {
type: 'angles';
data: angles;
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;
};
export type LogData = {
type: 'log';
data: string;
export type WifiSettings = {
hostname: string;
priority_RSSI: boolean;
wifi_networks: networkItem[];
};
export type WebSocketJsonMsg = AnglesData | LogData;
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;
};
export type NetworkItem = {
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;
};
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;
};
export type LightState = {
led_on: boolean;
};
export type BrokerSettings = {
mqtt_path: string;
name: string;
unique_id: string;
};
export type NTPStatus = {
status: number;
utc_time: string;
local_time: string;
server: string;
uptime: number;
};
export type NTPSettings = {
enabled: boolean;
server: string;
tz_label: string;
tz_format: 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;
};
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;
};
export type SystemInformation = Analytics & StaticSystemInformation;
export type MQTTStatus = {
enabled: boolean;
connected: boolean;
client_id: string;
last_error: string;
};
export type MQTTSettings = {
enabled: boolean;
uri: string;
username: string;
password: string;
client_id: string;
keep_alive: number;
clean_session: boolean;
};
+42 -20
View File
@@ -11,8 +11,6 @@ import {
GridHelper,
ArrowHelper,
Vector3,
LoaderUtils,
Object3D,
FogExp2,
CanvasTexture,
type ColorRepresentation,
@@ -20,7 +18,8 @@ import {
MeshPhongMaterial,
EquirectangularReflectionMapping,
ACESFilmicToneMapping,
MathUtils
MathUtils,
MeshStandardMaterial
} from 'three';
import { Sky } from 'three/addons/objects/Sky.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
@@ -75,7 +74,9 @@ export default class SceneBuilder {
public liveStreamTexture: CanvasTexture;
private fog: FogExp2;
private isLoaded: boolean = false;
public isDragging: boolean = false;
highlightMaterial: any;
sky: Sky;
constructor() {
this.scene = new Scene();
@@ -92,14 +93,14 @@ export default class SceneBuilder {
this.renderer.shadowMap.type = PCFSoftShadowMap;
this.renderer.toneMapping = ACESFilmicToneMapping;
this.renderer.toneMappingExposure = 0.85;
document.body.appendChild(this.renderer.domElement);
if (!parameters?.canvas) document.body.appendChild(this.renderer.domElement);
return this;
};
public addSky = () => {
const sky = new Sky();
sky.scale.setScalar(450000);
this.scene.add(sky);
this.sky = new Sky();
this.sky.scale.setScalar(450000);
this.scene.add(this.sky);
const effectController = {
turbidity: 10,
rayleigh: 3,
@@ -109,7 +110,7 @@ export default class SceneBuilder {
azimuth: 180,
exposure: this.renderer.toneMappingExposure
};
const uniforms = sky.material.uniforms;
const uniforms = this.sky.material.uniforms;
uniforms['turbidity'].value = effectController.turbidity;
uniforms['rayleigh'].value = effectController.rayleigh;
uniforms['mieCoefficient'].value = effectController.mieCoefficient;
@@ -126,13 +127,14 @@ export default class SceneBuilder {
public addPerspectiveCamera = (options: position) => {
this.camera = new PerspectiveCamera();
this.camera.position.set(options.x ?? 0, options.y ?? 0, options.z ?? 0);
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) => {
this.ground = new Mesh(new PlaneGeometry(), new ShadowMaterial({ side: 2 }));
var planeMaterial = new MeshStandardMaterial({ color: 0x808080, side: 2, opacity: 0.5 });
this.ground = new Mesh(new PlaneGeometry(), planeMaterial);
this.ground.rotation.x = -Math.PI / 2;
this.ground.scale.setScalar(30);
this.ground.position.set(options?.x ?? 0, options?.y ?? 0, options?.z ?? 0);
@@ -141,10 +143,11 @@ export default class SceneBuilder {
return this;
};
public addOrbitControls = (minDistance: number, maxDistance: number) => {
public addOrbitControls = (minDistance: number, maxDistance: number, autoRotate = true) => {
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.minDistance = minDistance;
this.controls.maxDistance = maxDistance;
this.controls.autoRotate = autoRotate;
this.controls.update();
return this;
};
@@ -158,11 +161,13 @@ export default class SceneBuilder {
public addDirectionalLight = (options: directionalLight) => {
const directionalLight = new DirectionalLight(options.color, options.intensity);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.setScalar(2048);
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
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);
directionalLight.shadow.radius = 5;
this.scene.add(directionalLight);
return this;
};
@@ -182,10 +187,20 @@ export default class SceneBuilder {
return this;
};
public handleResize = () => {
this.renderer.setSize(window.innerWidth, window.innerHeight);
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 = window.innerWidth / window.innerHeight;
this.camera.aspect = width / height;
this.camera.updateProjectionMatrix();
return this;
};
@@ -198,6 +213,7 @@ export default class SceneBuilder {
public startRenderLoop = () => {
this.renderer.setAnimationLoop(() => {
this.renderer.render(this.scene, this.camera);
this.controls.update();
this.handleRobotShadow();
if (this.callback) this.callback();
if (!this.liveStreamTexture) return;
@@ -282,8 +298,14 @@ export default class SceneBuilder {
this.setJointValue(joint.name, angle);
updateAngle(joint.name, angle);
};
dragControls.onDragStart = () => (this.controls.enabled = false);
dragControls.onDragEnd = () => (this.controls.enabled = true);
dragControls.onDragStart = () => {
this.controls.enabled = false;
this.isDragging = true;
};
dragControls.onDragEnd = () => {
this.controls.enabled = true;
this.isDragging = false;
};
dragControls.onHover = (joint: URDFMimicJoint) =>
this.highlightLinkGeometry(joint, false, highlightMaterial);
dragControls.onUnhover = (joint: URDFMimicJoint) =>
-1
View File
@@ -1,3 +1,2 @@
export { default as fileService } from './file-service';
export { default as socketService } from './socket-service';
export { default as resultService } from './result-service';
-93
View File
@@ -1,93 +0,0 @@
import { isConnected, socketData } from '$lib/stores';
import { Result, Ok } from '$lib/utilities';
import { resultService } from '$lib/services';
import { type WebSocketJsonMsg } from '$lib/models';
import type { Writable } from 'svelte/store';
type WebsocketOutData = string | ArrayBufferLike | Blob | ArrayBufferView;
// TODO
/**
* MOVE THE store to a store.ts file
*
* Make an object on the class that encapsulate all the stores
*
* Make the handle message function look up the type and set the value, to simplify the code
*/
class SocketService {
private socket!: WebSocket;
constructor() {}
public connect(url: string): void {
this.socket = new WebSocket(url);
this.socket.binaryType = 'arraybuffer';
this.socket.onopen = () => this.handleConnected();
this.socket.onclose = () => this.handleDisconnected();
this.socket.onmessage = (event: MessageEvent) =>
resultService.handleResult(this.handleMessage(event), 'SocketService');
this.socket.onerror = (error: Event) => console.log(error);
}
public send(data: WebsocketOutData): Result<void, string> {
if (this.socket.readyState === WebSocket.OPEN) {
this.socket.send(data);
return Ok.void();
}
return Result.err('The connection is not open');
}
public addPublisher(store: Writable<WebsocketOutData>, type?: string) {
const publish = (data: WebsocketOutData) =>
this.send(type ? JSON.stringify({ type, data }) : data);
store.subscribe(publish);
}
private handleConnected(): void {
isConnected.set(true);
}
private handleDisconnected(): void {
isConnected.set(false);
}
private getJsonFromMessage(msg: string): Result<WebSocketJsonMsg, string> {
try {
return Result.ok(JSON.parse(msg) as WebSocketJsonMsg);
} catch (error) {
return Result.err('Failed to parse socket message', error);
}
}
private handleBufferMessage(buffer: ArrayBuffer): Result<void, string> {
console.log(buffer);
return Ok.void();
}
private handleMessage(event: MessageEvent): Result<void, string> {
if (event.data instanceof ArrayBuffer) {
return this.handleBufferMessage(event.data);
}
let msgRes = this.getJsonFromMessage(event.data);
if (msgRes.isErr()) {
return msgRes;
}
const msg = msgRes.inner;
if (msg.type === 'log') {
socketData.logs.update((entries) => {
entries.push(msg.data);
return entries;
});
return Ok.void();
} else if (msg.data && msg.type in socketData) {
socketData[msg.type].set(msg.data);
return Ok.void();
}
return Result.err(`Got invalid msg: ${JSON.stringify(msg)}`);
}
}
export default new SocketService();
+1
View File
@@ -1,3 +1,4 @@
export * from './socket-store';
export * from './logging-store';
export * from './model-store';
export * from './socket';
+9 -2
View File
@@ -1,5 +1,5 @@
import type { ControllerInput } from '$lib/models';
import { persistentStore } from '$lib/utilities';
import { persistentStore } from '$lib/utilities/svelte-utilities';
import { writable, type Writable } from 'svelte/store';
export const emulateModel = writable(true);
@@ -12,7 +12,14 @@ export const modes = ['idle', 'rest', 'stand', 'walk'] as const;
export type Modes = (typeof modes)[number];
export const mode: Writable<Modes> = writable('idle');
export enum ModesEnum {
Idle,
Rest,
Stand,
Walk
}
export const mode: Writable<ModesEnum> = writable(ModesEnum.Idle);
export const outControllerData = writable(new Int8Array([0, 0, 0, 0, 0, 70, 0]));
+7 -9
View File
@@ -1,14 +1,16 @@
import { writable, type Writable } from 'svelte/store';
import { type angles } from '$lib/models';
export const isConnected = writable(false);
export const servoAngles: Writable<angles> = writable(new Int16Array(12).fill(0));
export const servoAnglesOut: Writable<number[]> = writable([
0, 45, -90, 0, 45, -90, 0, 45, -90, 0, 45, -90
]);
export const servoAngles: Writable<number[]> = writable([
0, 45, -90, 0, 45, -90, 0, 45, -90, 0, 45, -90
]);
export const logs = writable([] as string[]);
export const battery = writable({});
export const mpu = writable({ heading: 0 });
export const distances = writable({});
export const settings = writable({});
export const systemInfo = writable({} as number);
export interface socketDataCollection {
angles: Writable<angles>;
@@ -16,8 +18,6 @@ export interface socketDataCollection {
battery: Writable<unknown>;
mpu: Writable<unknown>;
distances: Writable<unknown>;
settings: Writable<unknown>;
systemInfo: Writable<unknown>;
}
export const socketData = {
@@ -25,7 +25,5 @@ export const socketData = {
logs,
battery,
mpu,
distances,
settings,
systemInfo
distances
};
+4 -5
View File
@@ -1,10 +1,9 @@
export const hostname = window.location.hostname;
export const hostname = 'localhost'; //window.location.hostname;
export const isSecure = window.location.protocol === 'https:';
export const isSecure = true; // window.location.protocol === 'https:';
export const location = import.meta.env.VITE_API_URL.replace('hostname', hostname);
export const location = 'localhost:5173'; //window.location; //import.meta.env.VITE_API_URL.replace('hostname', hostname);
const socketScheme = isSecure ? 'wss://' : 'ws://';
export const socketLocation =
socketScheme + import.meta.env.VITE_SOCKET_URL.replace('hostname', hostname);
export const socketLocation = socketScheme + location; // import.meta.env.VITE_SOCKET_URL.replace('hostname', hostname);
+1 -1
View File
@@ -33,7 +33,7 @@ export const loadModelAsync = async (
resolve(Result.err('Failed to load model', error));
}
},
(error) => reject(error)
(error) => resolve(Result.err('Failed to load model', error))
);
});
};
+3 -2
View File
@@ -1,14 +1,15 @@
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
export const isEmbeddedApp = import.meta.env.VITE_EMBEDDED_BUILD === 'true';
export const persistentStore = (key: string, initialValue: any) => {
const savedValue = JSON.parse(localStorage.getItem(key) as string);
const savedValue = browser ? JSON.parse(localStorage.getItem(key) as string) : null;
const data = savedValue !== null ? savedValue : initialValue;
const store = writable(data);
store.subscribe((value) => {
localStorage.setItem(key, JSON.stringify(value));
browser && localStorage.setItem(key, JSON.stringify(value));
});
return store;
-15
View File
@@ -1,15 +0,0 @@
import './app.css';
import './index.css';
import App from './App.svelte';
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js');
});
}
const app = new App({
target: document.getElementById('app') as HTMLElement
});
export default app;
-15
View File
@@ -1,15 +0,0 @@
<script lang="ts">
import Stream from '$components/Views/Stream.svelte';
import Model from '$components/Views/Model.svelte';
import Controls from '$components/Controls.svelte';
import { emulateModel } from '$lib/stores';
</script>
<div class="flex justify-center items-center w-full h-full">
{#if $emulateModel}
<Model />
{:else}
<Stream />
{/if}
<Controls />
</div>
-62
View File
@@ -1,62 +0,0 @@
<script lang="ts">
import { Link, Route, Router } from 'svelte-routing';
import Info from '../components/settings/Info.svelte';
import Log from '../components/settings/Log.svelte';
import Configuration from '../components/settings/Configuration.svelte';
import {
Icon,
InformationCircle,
BookOpen,
AdjustmentsVertical,
Cog6Tooth
} from 'svelte-hero-icons';
import Calibration from '../components/settings/Calibration.svelte';
export const page = '';
const menu = [
{
title: 'Calibration',
path: '/calibration',
icon: AdjustmentsVertical,
component: Calibration
},
{
title: 'System info',
path: '/info',
icon: InformationCircle,
component: Info
},
{
title: 'Log',
path: '/log',
icon: BookOpen,
component: Log
},
{
title: 'Settings',
path: '/settings',
icon: Cog6Tooth,
component: Configuration
}
];
</script>
<div class="pt-14 flex h-full">
<nav class="w-1/6 flex flex-col">
{#each menu as link}
<Link to={'/settings' + link.path}>
<div class="px-4 py-2 flex gap-2 items-center">
<Icon src={link.icon} size="24" />{link.title}
</div>
</Link>
{/each}
</nav>
<main class="w-full h-full">
<Router>
{#each menu as link}
<Route path={link.path} component={link.component}></Route>
{/each}
</Router>
</main>
</div>

Some files were not shown because too many files have changed in this diff Show More