📐 Adds socket-service
This commit is contained in:
+4
-4
@@ -2,9 +2,9 @@
|
|||||||
import { Router, Route } from 'svelte-routing';
|
import { Router, Route } from 'svelte-routing';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import TopBar from './components/TopBar.svelte';
|
import TopBar from './components/TopBar.svelte';
|
||||||
import { connect } from '$lib/socket';
|
import socketService from '$lib/services/socket-service';
|
||||||
import Controller from './routes/Controller.svelte';
|
import Controller from './routes/Controller.svelte';
|
||||||
import FileService from '$lib/services/file-service';
|
import fileService from '$lib/services/file-service';
|
||||||
import Settings from './routes/Settings.svelte';
|
import Settings from './routes/Settings.svelte';
|
||||||
import { jointNames, model } from '$lib/store';
|
import { jointNames, model } from '$lib/store';
|
||||||
import { loadModelAsync } from '$lib/utilities';
|
import { loadModelAsync } from '$lib/utilities';
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
export let url = window.location.pathname
|
export let url = window.location.pathname
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
connect(socketLocation);
|
socketService.connect(socketLocation);
|
||||||
registerFetchIntercept()
|
registerFetchIntercept()
|
||||||
const [urdf, JOINT_NAME] = await loadModelAsync('/spot_micro.urdf.xacro')
|
const [urdf, JOINT_NAME] = await loadModelAsync('/spot_micro.urdf.xacro')
|
||||||
jointNames.set(JOINT_NAME)
|
jointNames.set(JOINT_NAME)
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
window.fetch = async (...args) => {
|
window.fetch = async (...args) => {
|
||||||
const [resource, config] = args;
|
const [resource, config] = args;
|
||||||
let file: Result<Uint8Array | undefined, string>;
|
let file: Result<Uint8Array | undefined, string>;
|
||||||
file = await FileService.getFile(resource.toString());
|
file = await fileService.getFile(resource.toString());
|
||||||
return file.isOk()
|
return file.isOk()
|
||||||
? new Response(file.inner)
|
? new Response(file.inner)
|
||||||
: originalFetch(resource, config)
|
: originalFetch(resource, config)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import nipplejs from 'nipplejs';
|
import nipplejs from 'nipplejs';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { throttler } from '$lib/utilities';
|
import { throttler } from '$lib/utilities';
|
||||||
import { socket } from '$lib/socket';
|
import socketService from '$lib/services/socket-service';
|
||||||
import { emulateModel, input, outControllerData } from '$lib/store';
|
import { emulateModel, input, outControllerData } from '$lib/store';
|
||||||
|
|
||||||
let throttle = new throttler();
|
let throttle = new throttler();
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
outControllerData.set(data)
|
outControllerData.set(data)
|
||||||
|
|
||||||
if(!$emulateModel) $socket.send(data);
|
if(!$emulateModel) socketService.send(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { isConnected, status, socket } from '$lib/socket';
|
import socketService from '$lib/services/socket-service';
|
||||||
import { Icon, Bars3, XMark, Power, Battery100, Signal, SignalSlash } from 'svelte-hero-icons';
|
import { Icon, Bars3, XMark, Power, Battery100, Signal, SignalSlash } from 'svelte-hero-icons';
|
||||||
import { emulateModel } from '$lib/store';
|
import { emulateModel } from '$lib/store';
|
||||||
import { Link, useLocation } from 'svelte-routing'
|
import { Link, useLocation } from 'svelte-routing'
|
||||||
@@ -12,13 +12,14 @@
|
|||||||
let selected_view = views[0];
|
let selected_view = views[0];
|
||||||
let selected_modes = modes[0];
|
let selected_modes = modes[0];
|
||||||
let settingOpen = window.location.pathname.includes('/settings')
|
let settingOpen = window.location.pathname.includes('/settings')
|
||||||
|
let isConnected = socketService.isConnected
|
||||||
|
|
||||||
$: emulateModel.set(selected_view === views[0])
|
$: emulateModel.set(selected_view === views[0])
|
||||||
$: settingOpen = $location.pathname.includes('/settings')
|
$: settingOpen = $location.pathname.includes('/settings')
|
||||||
|
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
if ($isConnected) {
|
if ($isConnected) {
|
||||||
$socket.send(JSON.stringify({type:"system/stop"}))
|
socketService.send(JSON.stringify({type:"system/stop"}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { CanvasTexture, CircleGeometry, Mesh, MeshBasicMaterial} from 'three';
|
import { CanvasTexture, CircleGeometry, Mesh, MeshBasicMaterial} from 'three';
|
||||||
import {socket, angles, mpu } from '$lib/socket'
|
import socketService from '$lib/services/socket-service';
|
||||||
import { lerp } from '$lib/utilities';
|
import { lerp } from '$lib/utilities';
|
||||||
import uzip from 'uzip';
|
import uzip from 'uzip';
|
||||||
import { model, outControllerData } from '$lib/store';
|
import { model, outControllerData } from '$lib/store';
|
||||||
@@ -10,6 +10,9 @@ import { location } from '$lib/utilities';
|
|||||||
import FileService from '$lib/services/file-service';
|
import FileService from '$lib/services/file-service';
|
||||||
import SceneBuilder from '$lib/sceneBuilder';
|
import SceneBuilder from '$lib/sceneBuilder';
|
||||||
|
|
||||||
|
const angles = socketService.angles
|
||||||
|
const mpu = socketService.mpu
|
||||||
|
|
||||||
let sceneManager:SceneBuilder
|
let sceneManager:SceneBuilder
|
||||||
let canvas: HTMLCanvasElement, streamCanvas: HTMLCanvasElement, stream: HTMLImageElement
|
let canvas: HTMLCanvasElement, streamCanvas: HTMLCanvasElement, stream: HTMLImageElement
|
||||||
let context: CanvasRenderingContext2D, texture: CanvasTexture
|
let context: CanvasRenderingContext2D, texture: CanvasTexture
|
||||||
@@ -44,7 +47,7 @@ onMount(async () => {
|
|||||||
await createScene()
|
await createScene()
|
||||||
|
|
||||||
outControllerData.subscribe(data => {
|
outControllerData.subscribe(data => {
|
||||||
$socket.send(JSON.stringify({
|
socketService.send(JSON.stringify({
|
||||||
type: "kinematic/bodystate",
|
type: "kinematic/bodystate",
|
||||||
angles:[0, (data[1]-128)/3, (data[2]-128) / 4],
|
angles:[0, (data[1]-128)/3, (data[2]-128) / 4],
|
||||||
position:[(data[4]-128)/2, data[5], (data[3]-128)/2]}))
|
position:[(data[4]-128)/2, data[5], (data[3]-128)/2]}))
|
||||||
@@ -59,7 +62,6 @@ const cacheModelFiles = async () => {
|
|||||||
let data = await fetch("/stl.zip").then(data => data.arrayBuffer())
|
let data = await fetch("/stl.zip").then(data => data.arrayBuffer())
|
||||||
|
|
||||||
var files = uzip.parse(data);
|
var files = uzip.parse(data);
|
||||||
await FileService.openDatabase()
|
|
||||||
|
|
||||||
for(const [path, data] of Object.entries(files) as [path:string, data:Uint8Array][]){
|
for(const [path, data] of Object.entries(files) as [path:string, data:Uint8Array][]){
|
||||||
const url = new URL(path, window.location.href)
|
const url = new URL(path, window.location.href)
|
||||||
@@ -69,7 +71,7 @@ const cacheModelFiles = async () => {
|
|||||||
|
|
||||||
const updateAngles = (name:string, angle:number) => {
|
const updateAngles = (name:string, angle:number) => {
|
||||||
modelTargetAngles[servoNames.indexOf(name)] = angle * (180/Math.PI)
|
modelTargetAngles[servoNames.indexOf(name)] = angle * (180/Math.PI)
|
||||||
$socket.send(JSON.stringify({type:"kinematic/angle", angle:angle * (180/Math.PI), id:servoNames.indexOf(name)}))
|
socketService.send(JSON.stringify({type:"kinematic/angle", angle:angle * (180/Math.PI), id:servoNames.indexOf(name)}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const createScene = async () => {
|
const createScene = async () => {
|
||||||
@@ -117,7 +119,7 @@ const render = () => {
|
|||||||
const points = forwardKinematics.calculateFootpoints(modelAngles.map(ang => degToRad(ang)) as number[])
|
const points = forwardKinematics.calculateFootpoints(modelAngles.map(ang => degToRad(ang)) as number[])
|
||||||
robot.position.y = Math.max(...points.map(coord => coord[0] / 100)) - 2.7
|
robot.position.y = Math.max(...points.map(coord => coord[0] / 100)) - 2.7
|
||||||
robot.rotation.z = lerp(robot.rotation.z, degToRad($mpu.heading + 90), 0.1)
|
robot.rotation.z = lerp(robot.rotation.z, degToRad($mpu.heading + 90), 0.1)
|
||||||
modelTargetAngles = $angles
|
modelTargetAngles = $angles
|
||||||
|
|
||||||
handleVideoStream()
|
handleVideoStream()
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { socket, isConnected, settings } from "../../lib/socket";
|
import socketService from "$lib/services/socket-service";
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
|
let isConnected = socketService.isConnected
|
||||||
|
let settings = socketService.settings
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if ($isConnected) {
|
if ($isConnected) {
|
||||||
const message = JSON.stringify({type: 'system/settings'})
|
const message = JSON.stringify({type: 'system/settings'})
|
||||||
$socket.send(message)
|
socketService.send(message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { socket, isConnected, systemInfo } from "../../lib/socket";
|
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { humanFileSize } from "$lib/utilities";
|
import { humanFileSize } from "$lib/utilities";
|
||||||
|
import socketService from '$lib/services/socket-service';
|
||||||
|
|
||||||
|
let isConnected = socketService.isConnected
|
||||||
|
let settings = socketService.settings
|
||||||
|
let systemInfo = socketService.systemInfo
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if ($isConnected) {
|
if ($isConnected) {
|
||||||
const message = JSON.stringify({type: 'system/info'})
|
const message = JSON.stringify({type: 'system/info'})
|
||||||
$socket.send(message)
|
socketService.send(message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { socket, isConnected, log } from "../../lib/socket";
|
import socketService from "$lib/services/socket-service";
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
|
let isConnected = socketService.isConnected
|
||||||
|
let log = socketService.log
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if ($isConnected) {
|
if ($isConnected) {
|
||||||
const message = JSON.stringify({type: 'system/logs'})
|
const message = JSON.stringify({type: 'system/logs'})
|
||||||
$socket.send(message)
|
socketService.send(message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from './file-service'
|
export * from './file-service'
|
||||||
|
export * from './socket-service'
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { Result, Ok } from '$lib/utilities';
|
||||||
|
import { writable, type Writable } from 'svelte/store';
|
||||||
|
|
||||||
|
type WebsocketData = string | ArrayBufferLike | Blob | ArrayBufferView
|
||||||
|
|
||||||
|
class SocketService {
|
||||||
|
|
||||||
|
public isConnected = writable(false);
|
||||||
|
public angles = writable(new Int16Array(12).fill(0));
|
||||||
|
public log = writable([] as string[]);
|
||||||
|
public battery = writable({});
|
||||||
|
public mpu = writable({ heading: 0 });
|
||||||
|
public distances = writable({});
|
||||||
|
public settings = writable({});
|
||||||
|
public systemInfo = writable({} as number);
|
||||||
|
public dataBuffer = writable(new Float32Array(13));
|
||||||
|
public servoBuffer: Writable<Int16Array | number[]> = writable(new Int16Array(12));
|
||||||
|
public data = writable();
|
||||||
|
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: unknown) => this.handleMessage(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public send(data: WebsocketData): Result<void, string> {
|
||||||
|
if (this.socket.readyState === WebSocket.OPEN){
|
||||||
|
this.socket.send(data)
|
||||||
|
return Ok.void()
|
||||||
|
}
|
||||||
|
return Result.err("The connection is not open")
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleConnected(): void {
|
||||||
|
this.isConnected.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleDisconnected(): void {
|
||||||
|
this.isConnected.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMessage(event: any): void {
|
||||||
|
if (event.data instanceof ArrayBuffer) {
|
||||||
|
let buffer = new Int8Array(event.data);
|
||||||
|
if (buffer.length === 44) {
|
||||||
|
this.dataBuffer.set(new Float32Array(buffer.buffer));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let data = event.data;
|
||||||
|
try {
|
||||||
|
data = JSON.parse(event.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
}
|
||||||
|
switch (data.type) {
|
||||||
|
case "angles":
|
||||||
|
this.angles.set(data.angles);
|
||||||
|
break;
|
||||||
|
case "logs":
|
||||||
|
this.log.set(data.logs);
|
||||||
|
break;
|
||||||
|
case "log":
|
||||||
|
this.log.update(entries => { entries.push(data.log); return entries; });
|
||||||
|
break;
|
||||||
|
case "settings":
|
||||||
|
this.settings.set(data.settings);
|
||||||
|
case "info":
|
||||||
|
this.systemInfo.set(data.info);
|
||||||
|
break;
|
||||||
|
case "mpu":
|
||||||
|
this.mpu.set(data.mpu);
|
||||||
|
break;
|
||||||
|
case "distances":
|
||||||
|
this.distances.set(data.distances);
|
||||||
|
break;
|
||||||
|
case "battery":
|
||||||
|
this.battery.set(data.battery);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new SocketService();
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import { writable, type Writable } from 'svelte/store';
|
|
||||||
|
|
||||||
export type WebSocketStatus = 'OPEN' | 'CONNECTING' | 'CLOSED'
|
|
||||||
|
|
||||||
export const isConnected = writable(false)
|
|
||||||
|
|
||||||
export const angles = writable(new Int16Array(12).fill(0))
|
|
||||||
export const log = writable([])
|
|
||||||
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 const dataBuffer = writable(new Float32Array(13))
|
|
||||||
|
|
||||||
export const servoBuffer:Writable<Int16Array|number[]> = writable(new Int16Array(12))
|
|
||||||
|
|
||||||
export const data = writable();
|
|
||||||
|
|
||||||
export const status:Writable<WebSocketStatus> = writable('CLOSED')
|
|
||||||
|
|
||||||
export const socket:Writable<WebSocket> = writable()
|
|
||||||
|
|
||||||
export const connect = (url:string) => {
|
|
||||||
status.set('CONNECTING')
|
|
||||||
let _socket = new WebSocket(url);
|
|
||||||
_socket.binaryType = "arraybuffer";
|
|
||||||
_socket.onopen = _connected;
|
|
||||||
_socket.onclose = _disconnected;
|
|
||||||
_socket.onmessage = _message;
|
|
||||||
socket.set(_socket)
|
|
||||||
|
|
||||||
servoBuffer.subscribe(data => {
|
|
||||||
if(_socket.readyState !== 1) return
|
|
||||||
const buffer = []
|
|
||||||
buffer[0] = 1
|
|
||||||
buffer.push(...data)
|
|
||||||
_socket.send(new Int16Array(buffer))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const _connected = () => {
|
|
||||||
status.set('OPEN')
|
|
||||||
isConnected.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const _disconnected = () => {
|
|
||||||
status.set('CLOSED')
|
|
||||||
isConnected.set(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const _message = (event:any) => {
|
|
||||||
if (event.data instanceof ArrayBuffer) {
|
|
||||||
let buffer = new Int8Array(event.data);
|
|
||||||
if(buffer.length === 44) {
|
|
||||||
dataBuffer.set(new Float32Array(buffer.buffer) )
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let data = event.data
|
|
||||||
try {
|
|
||||||
data = JSON.parse(event.data)
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error)
|
|
||||||
}
|
|
||||||
switch (data.type) {
|
|
||||||
case "angles":
|
|
||||||
angles.set(data.angles)
|
|
||||||
break
|
|
||||||
case "logs":
|
|
||||||
log.set(data.logs)
|
|
||||||
break
|
|
||||||
case "log":
|
|
||||||
log.update(entries => {entries.push(data.log); return entries})
|
|
||||||
break
|
|
||||||
case "settings":
|
|
||||||
settings.set(data.settings)
|
|
||||||
case "info":
|
|
||||||
systemInfo.set(data.info)
|
|
||||||
break
|
|
||||||
case "mpu":
|
|
||||||
mpu.set(data.mpu)
|
|
||||||
break
|
|
||||||
case "distances":
|
|
||||||
distances.set(data.distances)
|
|
||||||
break
|
|
||||||
case "battery":
|
|
||||||
battery.set(data.battery)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -33,4 +33,12 @@ export class Ok<T> {
|
|||||||
static new<T>(inner: T): Ok<T> {
|
static new<T>(inner: T): Ok<T> {
|
||||||
return new Ok<T>(inner)
|
return new Ok<T>(inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an empty `Ok`
|
||||||
|
* @returns `Ok(void)`
|
||||||
|
*/
|
||||||
|
static void(): Ok<void> {
|
||||||
|
return new Ok(undefined)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user