🎨 format
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Camera from './Camera.svelte';
|
||||
import Camera from './Camera.svelte'
|
||||
</script>
|
||||
|
||||
<div class="mx-0 my-1 flex flex-col space-y-4 sm:mx-8 sm:my-8">
|
||||
<Camera />
|
||||
<Camera />
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import SettingsCard from "$lib/components/SettingsCard.svelte";
|
||||
import CameraSetting from './CameraSetting.svelte';
|
||||
import Stream from '$lib/components/Stream.svelte';
|
||||
import { Camera } from "$lib/components/icons";
|
||||
import SettingsCard from '$lib/components/SettingsCard.svelte'
|
||||
import CameraSetting from './CameraSetting.svelte'
|
||||
import Stream from '$lib/components/Stream.svelte'
|
||||
import { Camera } from '$lib/components/icons'
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
{#snippet icon()}
|
||||
<Camera class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
{/snippet}
|
||||
<Camera class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<span >Camera</span>
|
||||
{/snippet}
|
||||
<span>Camera</span>
|
||||
{/snippet}
|
||||
<Stream />
|
||||
<CameraSetting />
|
||||
</SettingsCard>
|
||||
</SettingsCard>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { api } from '$lib/api';
|
||||
import Spinner from '$lib/components/Spinner.svelte';
|
||||
import type { CameraSettings } from '$lib/types/models';
|
||||
let settings:CameraSettings = $state()
|
||||
import { api } from '$lib/api'
|
||||
import Spinner from '$lib/components/Spinner.svelte'
|
||||
import type { CameraSettings } from '$lib/types/models'
|
||||
let settings: CameraSettings = $state()
|
||||
|
||||
const getCameraSettings = async () => {
|
||||
const result = await api.get<CameraSettings>('/api/camera/settings')
|
||||
if (result.isErr()){
|
||||
console.error("An error occurred", result.inner);
|
||||
if (result.isErr()) {
|
||||
console.error('An error occurred', result.inner)
|
||||
return
|
||||
}
|
||||
settings = result.inner
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
const updateCameraSettings = async () => {
|
||||
const result = await api.post<CameraSettings>('/api/camera/settings', settings)
|
||||
if (result.isErr()){
|
||||
console.error("An error occurred", result.inner);
|
||||
if (result.isErr()) {
|
||||
console.error('An error occurred', result.inner)
|
||||
return
|
||||
}
|
||||
settings = result.inner
|
||||
@@ -25,27 +25,47 @@
|
||||
|
||||
{#await getCameraSettings()}
|
||||
<Spinner />
|
||||
{:then _}
|
||||
{:then _}
|
||||
<div class="flex flex-col gap-1">
|
||||
<button class="btn btn-primary" type="button" onclick={updateCameraSettings}>Update camera settings</button>
|
||||
<button class="btn btn-primary" type="button" onclick={updateCameraSettings}
|
||||
>Update camera settings</button
|
||||
>
|
||||
|
||||
<label for="brightness">
|
||||
Brightness {settings.brightness}
|
||||
<input type="range" min="-2" max="2" class="range range-xs" bind:value={settings.brightness}/>
|
||||
<input
|
||||
type="range"
|
||||
min="-2"
|
||||
max="2"
|
||||
class="range range-xs"
|
||||
bind:value={settings.brightness}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label for="contrast">
|
||||
Contrast {settings.contrast}
|
||||
<input type="range" min="-2" max="2" class="range range-xs" bind:value={settings.contrast}/>
|
||||
<input
|
||||
type="range"
|
||||
min="-2"
|
||||
max="2"
|
||||
class="range range-xs"
|
||||
bind:value={settings.contrast}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label for="framesize">
|
||||
FrameSize {settings.framesize}
|
||||
<input type="range" min="0" max="10" class="range range-xs" bind:value={settings.framesize}/>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="10"
|
||||
class="range range-xs"
|
||||
bind:value={settings.framesize}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="cursor-pointer flex items-center justify-between">
|
||||
Vertical flip
|
||||
Vertical flip
|
||||
<input type="checkbox" class="toggle" bind:checked={settings.vflip} />
|
||||
</label>
|
||||
|
||||
@@ -56,7 +76,10 @@
|
||||
|
||||
<label for="special_effect" class="flex items-center">
|
||||
<span class="basis-1/2">Special Effect</span>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs" bind:value={settings.special_effect}>
|
||||
<select
|
||||
class="select select-bordered select-sm w-full max-w-xs"
|
||||
bind:value={settings.special_effect}
|
||||
>
|
||||
<option value={0}>No effect</option>
|
||||
<option value={1}>Negative</option>
|
||||
<option value={2}>Grayscale</option>
|
||||
@@ -67,4 +90,4 @@
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
{/await}
|
||||
{/await}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import I2C from './i2c.svelte'
|
||||
import I2C from './i2c.svelte'
|
||||
</script>
|
||||
|
||||
<div class="mx-0 my-1 flex flex-col space-y-4 sm:mx-8 sm:my-8">
|
||||
<I2C />
|
||||
<I2C />
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PageLoad } from './$types'
|
||||
|
||||
export const load = (async () => {
|
||||
return {
|
||||
title: 'I2C'
|
||||
}
|
||||
return {
|
||||
title: 'I2C'
|
||||
}
|
||||
}) satisfies PageLoad
|
||||
|
||||
@@ -1,79 +1,79 @@
|
||||
<script lang="ts">
|
||||
import SettingsCard from '$lib/components/SettingsCard.svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import { socket } from '$lib/stores'
|
||||
import { MessageTopic, type I2CDevice } from '$lib/types/models'
|
||||
import { Connection } from '$lib/components/icons'
|
||||
import I2CSetting from './i2cSetting.svelte'
|
||||
import SettingsCard from '$lib/components/SettingsCard.svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import { socket } from '$lib/stores'
|
||||
import { MessageTopic, type I2CDevice } from '$lib/types/models'
|
||||
import { Connection } from '$lib/components/icons'
|
||||
import I2CSetting from './i2cSetting.svelte'
|
||||
|
||||
const i2cDevices = [
|
||||
{ address: 30, part_number: 'HMC5883', name: '3-Axis Digital Compass/Magnetometer IC' },
|
||||
{ address: 41, part_number: 'BNO055', name: '9-Axis Absolute Orientation Sensor' },
|
||||
{ address: 64, part_number: 'PCA9685', name: '16-channel PWM driver default address' },
|
||||
{ address: 72, part_number: 'ADS1115', name: '4-channel 16-bit ADC' },
|
||||
{
|
||||
address: 104,
|
||||
part_number: 'MPU6050',
|
||||
name: 'Six-Axis (Gyro + Accelerometer) MEMS MotionTracking™ Devices'
|
||||
},
|
||||
{ address: 115, part_number: 'PAJ7620U2', name: 'Gesture sensor' },
|
||||
{ address: 119, part_number: 'BMP085', name: 'Temp/Barometric' }
|
||||
]
|
||||
const i2cDevices = [
|
||||
{ address: 30, part_number: 'HMC5883', name: '3-Axis Digital Compass/Magnetometer IC' },
|
||||
{ address: 41, part_number: 'BNO055', name: '9-Axis Absolute Orientation Sensor' },
|
||||
{ address: 64, part_number: 'PCA9685', name: '16-channel PWM driver default address' },
|
||||
{ address: 72, part_number: 'ADS1115', name: '4-channel 16-bit ADC' },
|
||||
{
|
||||
address: 104,
|
||||
part_number: 'MPU6050',
|
||||
name: 'Six-Axis (Gyro + Accelerometer) MEMS MotionTracking™ Devices'
|
||||
},
|
||||
{ address: 115, part_number: 'PAJ7620U2', name: 'Gesture sensor' },
|
||||
{ address: 119, part_number: 'BMP085', name: 'Temp/Barometric' }
|
||||
]
|
||||
|
||||
let active_devices: I2CDevice[] = $state([])
|
||||
let active_devices: I2CDevice[] = $state([])
|
||||
|
||||
let isLoading = $state(false)
|
||||
let isLoading = $state(false)
|
||||
|
||||
onMount(() => {
|
||||
socket.on(MessageTopic.i2cScan, handleScan)
|
||||
triggerScan()
|
||||
return () => socket.off(MessageTopic.i2cScan, handleScan)
|
||||
})
|
||||
onMount(() => {
|
||||
socket.on(MessageTopic.i2cScan, handleScan)
|
||||
triggerScan()
|
||||
return () => socket.off(MessageTopic.i2cScan, handleScan)
|
||||
})
|
||||
|
||||
const handleScan = (data: any) => {
|
||||
active_devices = data.addresses.map(
|
||||
(address: number) =>
|
||||
i2cDevices.find(device => device.address === address) || {
|
||||
address,
|
||||
part_number: 'Unknown',
|
||||
name: 'Unknown'
|
||||
}
|
||||
)
|
||||
isLoading = false
|
||||
}
|
||||
const handleScan = (data: any) => {
|
||||
active_devices = data.addresses.map(
|
||||
(address: number) =>
|
||||
i2cDevices.find(device => device.address === address) || {
|
||||
address,
|
||||
part_number: 'Unknown',
|
||||
name: 'Unknown'
|
||||
}
|
||||
)
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
const triggerScan = () => {
|
||||
isLoading = true
|
||||
socket.sendEvent(MessageTopic.i2cScan, '')
|
||||
}
|
||||
const triggerScan = () => {
|
||||
isLoading = true
|
||||
socket.sendEvent(MessageTopic.i2cScan, '')
|
||||
}
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
{#snippet icon()}
|
||||
<Connection class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<span>I<sup>2</sup>C</span>
|
||||
{/snippet}
|
||||
{#snippet right()}
|
||||
<button class="btn btn-primary" onclick={triggerScan} disabled={isLoading}>
|
||||
{#if isLoading}
|
||||
<span class="loading loading-ring loading-xs"></span>
|
||||
{:else}
|
||||
Scan
|
||||
{/if}
|
||||
</button>
|
||||
{/snippet}
|
||||
{#snippet icon()}
|
||||
<Connection class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<span>I<sup>2</sup>C</span>
|
||||
{/snippet}
|
||||
{#snippet right()}
|
||||
<button class="btn btn-primary" onclick={triggerScan} disabled={isLoading}>
|
||||
{#if isLoading}
|
||||
<span class="loading loading-ring loading-xs"></span>
|
||||
{:else}
|
||||
Scan
|
||||
{/if}
|
||||
</button>
|
||||
{/snippet}
|
||||
|
||||
<I2CSetting />
|
||||
<I2CSetting />
|
||||
|
||||
<div class="grid">
|
||||
{#if active_devices.length === 0}
|
||||
<div>No I2C devices found</div>
|
||||
{:else}
|
||||
{#each active_devices as device}
|
||||
<div>[{device.address.toString(16)}] {device.part_number} - {device.name}</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid">
|
||||
{#if active_devices.length === 0}
|
||||
<div>No I2C devices found</div>
|
||||
{:else}
|
||||
{#each active_devices as device}
|
||||
<div>[{device.address.toString(16)}] {device.part_number} - {device.name}</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</SettingsCard>
|
||||
|
||||
@@ -1,99 +1,107 @@
|
||||
<script lang="ts">
|
||||
import { Cancel, Edit, EditOff, Power } from '$lib/components/icons'
|
||||
import { socket } from '$lib/stores'
|
||||
import { MessageTopic, type PeripheralsConfiguration } from '$lib/types/models'
|
||||
import { onMount } from 'svelte'
|
||||
import { modals } from 'svelte-modals'
|
||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'
|
||||
import { Cancel, Edit, EditOff, Power } from '$lib/components/icons'
|
||||
import { socket } from '$lib/stores'
|
||||
import { MessageTopic, type PeripheralsConfiguration } from '$lib/types/models'
|
||||
import { onMount } from 'svelte'
|
||||
import { modals } from 'svelte-modals'
|
||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'
|
||||
|
||||
let settings: PeripheralsConfiguration | null = $state(null)
|
||||
let isEditing = $state(false)
|
||||
let settings: PeripheralsConfiguration | null = $state(null)
|
||||
let isEditing = $state(false)
|
||||
|
||||
onMount(() => {
|
||||
socket.on(MessageTopic.peripheralSettings, handleSettings)
|
||||
socket.sendEvent(MessageTopic.peripheralSettings, '')
|
||||
return () => socket.off(MessageTopic.peripheralSettings, handleSettings)
|
||||
})
|
||||
|
||||
const handleSettings = (data: any) => {
|
||||
settings = data
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
modals.open(ConfirmDialog, {
|
||||
title: 'Confirm configuration',
|
||||
message:
|
||||
'Are you sure you want to save this configuration? The operation cannot be undone. Please make sure you have the correct settings.',
|
||||
labels: {
|
||||
cancel: { label: 'Cancel', icon: Cancel },
|
||||
confirm: { label: 'Confirm', icon: Power }
|
||||
},
|
||||
onConfirm: () => {
|
||||
modals.close()
|
||||
socket.sendEvent(MessageTopic.peripheralSettings, settings)
|
||||
}
|
||||
onMount(() => {
|
||||
socket.on(MessageTopic.peripheralSettings, handleSettings)
|
||||
socket.sendEvent(MessageTopic.peripheralSettings, '')
|
||||
return () => socket.off(MessageTopic.peripheralSettings, handleSettings)
|
||||
})
|
||||
}
|
||||
|
||||
const Icon = $derived(isEditing ? EditOff : Edit)
|
||||
const handleSettings = (data: any) => {
|
||||
settings = data
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
modals.open(ConfirmDialog, {
|
||||
title: 'Confirm configuration',
|
||||
message:
|
||||
'Are you sure you want to save this configuration? The operation cannot be undone. Please make sure you have the correct settings.',
|
||||
labels: {
|
||||
cancel: { label: 'Cancel', icon: Cancel },
|
||||
confirm: { label: 'Confirm', icon: Power }
|
||||
},
|
||||
onConfirm: () => {
|
||||
modals.close()
|
||||
socket.sendEvent(MessageTopic.peripheralSettings, settings)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const Icon = $derived(isEditing ? EditOff : Edit)
|
||||
</script>
|
||||
|
||||
{#if settings}
|
||||
<div class="collapse bg-base-100 border-base-300 border">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title font-semibold">Configuration</div>
|
||||
<div class="collapse-content text-sm">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="sda" class="input validator">
|
||||
SDA
|
||||
<div class="collapse bg-base-100 border-base-300 border">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title font-semibold">Configuration</div>
|
||||
<div class="collapse-content text-sm">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="sda" class="input validator">
|
||||
SDA
|
||||
|
||||
<input
|
||||
id="sda"
|
||||
type="number"
|
||||
required
|
||||
placeholder="Type a number between 1 to 48"
|
||||
min="0"
|
||||
max="48"
|
||||
title="SDA pin number (0-48)"
|
||||
disabled={!isEditing}
|
||||
bind:value={settings.sda} />
|
||||
</label>
|
||||
<label for="scl" class="input validator">
|
||||
SCL
|
||||
<input
|
||||
id="sda"
|
||||
type="number"
|
||||
required
|
||||
placeholder="Type a number between 1 to 48"
|
||||
min="0"
|
||||
max="48"
|
||||
title="SDA pin number (0-48)"
|
||||
disabled={!isEditing}
|
||||
bind:value={settings.sda}
|
||||
/>
|
||||
</label>
|
||||
<label for="scl" class="input validator">
|
||||
SCL
|
||||
|
||||
<input
|
||||
id="scl"
|
||||
type="number"
|
||||
required
|
||||
placeholder="Type a number between 1 to 48"
|
||||
min="1"
|
||||
max="48"
|
||||
title="SCL pin number (0-48)"
|
||||
disabled={!isEditing}
|
||||
bind:value={settings.scl} />
|
||||
</label>
|
||||
<label class="input validator" for="frequency">
|
||||
Frequency
|
||||
<input
|
||||
id="frequency"
|
||||
type="number"
|
||||
required
|
||||
placeholder="Type a number between 100000 to 430000"
|
||||
min="100000"
|
||||
max="430000"
|
||||
title="I2C frequency in Hz"
|
||||
disabled={!isEditing}
|
||||
bind:value={settings.frequency} />
|
||||
</label>
|
||||
<div>
|
||||
<button class="btn btn-outline btn-primary" onclick={() => (isEditing = !isEditing)}>
|
||||
<Icon class="h-6 w-6" />
|
||||
</button>
|
||||
{#if isEditing}
|
||||
<button class="btn btn-outline btn-primary" onclick={handleSave}>Save</button>
|
||||
{/if}
|
||||
<input
|
||||
id="scl"
|
||||
type="number"
|
||||
required
|
||||
placeholder="Type a number between 1 to 48"
|
||||
min="1"
|
||||
max="48"
|
||||
title="SCL pin number (0-48)"
|
||||
disabled={!isEditing}
|
||||
bind:value={settings.scl}
|
||||
/>
|
||||
</label>
|
||||
<label class="input validator" for="frequency">
|
||||
Frequency
|
||||
<input
|
||||
id="frequency"
|
||||
type="number"
|
||||
required
|
||||
placeholder="Type a number between 100000 to 430000"
|
||||
min="100000"
|
||||
max="430000"
|
||||
title="I2C frequency in Hz"
|
||||
disabled={!isEditing}
|
||||
bind:value={settings.frequency}
|
||||
/>
|
||||
</label>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-outline btn-primary"
|
||||
onclick={() => (isEditing = !isEditing)}
|
||||
>
|
||||
<Icon class="h-6 w-6" />
|
||||
</button>
|
||||
{#if isEditing}
|
||||
<button class="btn btn-outline btn-primary" onclick={handleSave}
|
||||
>Save</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import IMU from './imu.svelte';
|
||||
import IMU from './imu.svelte'
|
||||
</script>
|
||||
|
||||
<div class="mx-0 my-1 flex flex-col space-y-4 sm:mx-8 sm:my-8">
|
||||
<IMU />
|
||||
<IMU />
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,253 +1,256 @@
|
||||
<script lang="ts">
|
||||
import SettingsCard from '$lib/components/SettingsCard.svelte'
|
||||
import { imu } from '$lib/stores/imu'
|
||||
import { Chart, registerables } from 'chart.js'
|
||||
import { cubicOut } from 'svelte/easing'
|
||||
import { slide } from 'svelte/transition'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import { socket } from '$lib/stores'
|
||||
import { MessageTopic, type IMU } from '$lib/types/models'
|
||||
import { useFeatureFlags } from '$lib/stores/featureFlags'
|
||||
import { Rotate3d } from '$lib/components/icons'
|
||||
import SettingsCard from '$lib/components/SettingsCard.svelte'
|
||||
import { imu } from '$lib/stores/imu'
|
||||
import { Chart, registerables } from 'chart.js'
|
||||
import { cubicOut } from 'svelte/easing'
|
||||
import { slide } from 'svelte/transition'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import { socket } from '$lib/stores'
|
||||
import { MessageTopic, type IMU } from '$lib/types/models'
|
||||
import { useFeatureFlags } from '$lib/stores/featureFlags'
|
||||
import { Rotate3d } from '$lib/components/icons'
|
||||
|
||||
Chart.register(...registerables)
|
||||
Chart.register(...registerables)
|
||||
|
||||
const features = useFeatureFlags()
|
||||
let intervalId: ReturnType<typeof setInterval> | number
|
||||
const features = useFeatureFlags()
|
||||
let intervalId: ReturnType<typeof setInterval> | number
|
||||
|
||||
let angleChartElement: HTMLCanvasElement
|
||||
let tempChartElement: HTMLCanvasElement
|
||||
let altitudeChartElement: HTMLCanvasElement
|
||||
let angleChartElement: HTMLCanvasElement
|
||||
let tempChartElement: HTMLCanvasElement
|
||||
let altitudeChartElement: HTMLCanvasElement
|
||||
|
||||
let angleChart: Chart
|
||||
let tempChart: Chart
|
||||
let altitudeChart: Chart
|
||||
let angleChart: Chart
|
||||
let tempChart: Chart
|
||||
let altitudeChart: Chart
|
||||
|
||||
const getChartColors = () => {
|
||||
const style = getComputedStyle(document.body)
|
||||
return {
|
||||
primary: style.getPropertyValue('--color-primary'),
|
||||
secondary: style.getPropertyValue('--color-secondary'),
|
||||
accent: style.getPropertyValue('--color-accent'),
|
||||
background: style.getPropertyValue('--color-background')
|
||||
}
|
||||
}
|
||||
|
||||
const createBaseChartConfig = (bgColor: string) => ({
|
||||
maintainAspectRatio: false,
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
tooltip: { mode: 'index' as const, intersect: false }
|
||||
},
|
||||
elements: { point: { radius: 1 } },
|
||||
scales: {
|
||||
x: {
|
||||
grid: { color: bgColor },
|
||||
ticks: { color: bgColor },
|
||||
display: false
|
||||
},
|
||||
y: {
|
||||
type: 'linear' as const,
|
||||
position: 'left' as const,
|
||||
min: 0,
|
||||
max: 10,
|
||||
grid: { color: bgColor },
|
||||
ticks: { color: bgColor },
|
||||
border: { color: bgColor }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const initializeCharts = () => {
|
||||
const colors = getChartColors()
|
||||
const baseConfig = createBaseChartConfig(colors.background)
|
||||
|
||||
angleChart = new Chart(angleChartElement, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: 'x',
|
||||
borderColor: colors.primary,
|
||||
backgroundColor: colors.primary,
|
||||
borderWidth: 2,
|
||||
data: $imu.x,
|
||||
yAxisID: 'y'
|
||||
},
|
||||
{
|
||||
label: 'y',
|
||||
borderColor: colors.secondary,
|
||||
backgroundColor: colors.secondary,
|
||||
borderWidth: 2,
|
||||
data: $imu.y,
|
||||
yAxisID: 'y'
|
||||
},
|
||||
{
|
||||
label: 'z',
|
||||
borderColor: colors.accent,
|
||||
backgroundColor: colors.accent,
|
||||
borderWidth: 2,
|
||||
data: $imu.z,
|
||||
yAxisID: 'y'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
...baseConfig,
|
||||
scales: {
|
||||
...baseConfig.scales,
|
||||
y: {
|
||||
...baseConfig.scales.y,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Angle [°]',
|
||||
color: colors.background,
|
||||
font: { size: 16, weight: 'bold' }
|
||||
}
|
||||
}
|
||||
const getChartColors = () => {
|
||||
const style = getComputedStyle(document.body)
|
||||
return {
|
||||
primary: style.getPropertyValue('--color-primary'),
|
||||
secondary: style.getPropertyValue('--color-secondary'),
|
||||
accent: style.getPropertyValue('--color-accent'),
|
||||
background: style.getPropertyValue('--color-background')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
tempChart = new Chart(tempChartElement, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Barometer temperature',
|
||||
borderColor: colors.secondary,
|
||||
backgroundColor: colors.secondary,
|
||||
borderWidth: 2,
|
||||
data: $imu.bmp_temp,
|
||||
yAxisID: 'y'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
...baseConfig,
|
||||
scales: {
|
||||
...baseConfig.scales,
|
||||
y: {
|
||||
...baseConfig.scales.y,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Temperature [C°]',
|
||||
color: colors.background,
|
||||
font: { size: 16, weight: 'bold' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
altitudeChart = new Chart(altitudeChartElement, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Altitude',
|
||||
borderColor: colors.primary,
|
||||
backgroundColor: colors.primary,
|
||||
borderWidth: 2,
|
||||
data: $imu.altitude,
|
||||
yAxisID: 'y'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
...baseConfig,
|
||||
scales: {
|
||||
...baseConfig.scales,
|
||||
y: {
|
||||
...baseConfig.scales.y,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Altitude [M]',
|
||||
color: colors.background,
|
||||
font: { size: 16, weight: 'bold' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateChartData = (chart: Chart, data: number[], label: string) => {
|
||||
chart.data.labels = data
|
||||
chart.data.datasets[0].data = data
|
||||
chart.options.scales!.y!.min = Math.min(...data) - 1
|
||||
chart.options.scales!.y!.max = Math.max(...data) + 1
|
||||
chart.update('none')
|
||||
}
|
||||
|
||||
const updateData = () => {
|
||||
if ($features.imu) {
|
||||
angleChart.data.labels = $imu.x
|
||||
angleChart.data.datasets[0].data = $imu.x
|
||||
angleChart.data.datasets[1].data = $imu.y
|
||||
angleChart.data.datasets[2].data = $imu.z
|
||||
|
||||
const allValues = [...$imu.x, ...$imu.y, ...$imu.z]
|
||||
angleChart.options.scales!.y!.min = Math.min(...allValues) - 1
|
||||
angleChart.options.scales!.y!.max = Math.max(...allValues) + 1
|
||||
angleChart.update('none')
|
||||
}
|
||||
|
||||
if ($features.bmp) {
|
||||
updateChartData(tempChart, $imu.bmp_temp, 'Temperature')
|
||||
updateChartData(altitudeChart, $imu.altitude, 'Altitude')
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
socket.on(MessageTopic.imu, (data: IMU) => {
|
||||
console.log(data)
|
||||
imu.addData(data)
|
||||
const createBaseChartConfig = (bgColor: string) => ({
|
||||
maintainAspectRatio: false,
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
tooltip: { mode: 'index' as const, intersect: false }
|
||||
},
|
||||
elements: { point: { radius: 1 } },
|
||||
scales: {
|
||||
x: {
|
||||
grid: { color: bgColor },
|
||||
ticks: { color: bgColor },
|
||||
display: false
|
||||
},
|
||||
y: {
|
||||
type: 'linear' as const,
|
||||
position: 'left' as const,
|
||||
min: 0,
|
||||
max: 10,
|
||||
grid: { color: bgColor },
|
||||
ticks: { color: bgColor },
|
||||
border: { color: bgColor }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
initializeCharts()
|
||||
intervalId = setInterval(updateData, 200)
|
||||
})
|
||||
const initializeCharts = () => {
|
||||
const colors = getChartColors()
|
||||
const baseConfig = createBaseChartConfig(colors.background)
|
||||
|
||||
onDestroy(() => {
|
||||
socket.off(MessageTopic.imu)
|
||||
clearInterval(intervalId)
|
||||
})
|
||||
angleChart = new Chart(angleChartElement, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: 'x',
|
||||
borderColor: colors.primary,
|
||||
backgroundColor: colors.primary,
|
||||
borderWidth: 2,
|
||||
data: $imu.x,
|
||||
yAxisID: 'y'
|
||||
},
|
||||
{
|
||||
label: 'y',
|
||||
borderColor: colors.secondary,
|
||||
backgroundColor: colors.secondary,
|
||||
borderWidth: 2,
|
||||
data: $imu.y,
|
||||
yAxisID: 'y'
|
||||
},
|
||||
{
|
||||
label: 'z',
|
||||
borderColor: colors.accent,
|
||||
backgroundColor: colors.accent,
|
||||
borderWidth: 2,
|
||||
data: $imu.z,
|
||||
yAxisID: 'y'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
...baseConfig,
|
||||
scales: {
|
||||
...baseConfig.scales,
|
||||
y: {
|
||||
...baseConfig.scales.y,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Angle [°]',
|
||||
color: colors.background,
|
||||
font: { size: 16, weight: 'bold' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
tempChart = new Chart(tempChartElement, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Barometer temperature',
|
||||
borderColor: colors.secondary,
|
||||
backgroundColor: colors.secondary,
|
||||
borderWidth: 2,
|
||||
data: $imu.bmp_temp,
|
||||
yAxisID: 'y'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
...baseConfig,
|
||||
scales: {
|
||||
...baseConfig.scales,
|
||||
y: {
|
||||
...baseConfig.scales.y,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Temperature [C°]',
|
||||
color: colors.background,
|
||||
font: { size: 16, weight: 'bold' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
altitudeChart = new Chart(altitudeChartElement, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Altitude',
|
||||
borderColor: colors.primary,
|
||||
backgroundColor: colors.primary,
|
||||
borderWidth: 2,
|
||||
data: $imu.altitude,
|
||||
yAxisID: 'y'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
...baseConfig,
|
||||
scales: {
|
||||
...baseConfig.scales,
|
||||
y: {
|
||||
...baseConfig.scales.y,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Altitude [M]',
|
||||
color: colors.background,
|
||||
font: { size: 16, weight: 'bold' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateChartData = (chart: Chart, data: number[], label: string) => {
|
||||
chart.data.labels = data
|
||||
chart.data.datasets[0].data = data
|
||||
chart.options.scales!.y!.min = Math.min(...data) - 1
|
||||
chart.options.scales!.y!.max = Math.max(...data) + 1
|
||||
chart.update('none')
|
||||
}
|
||||
|
||||
const updateData = () => {
|
||||
if ($features.imu) {
|
||||
angleChart.data.labels = $imu.x
|
||||
angleChart.data.datasets[0].data = $imu.x
|
||||
angleChart.data.datasets[1].data = $imu.y
|
||||
angleChart.data.datasets[2].data = $imu.z
|
||||
|
||||
const allValues = [...$imu.x, ...$imu.y, ...$imu.z]
|
||||
angleChart.options.scales!.y!.min = Math.min(...allValues) - 1
|
||||
angleChart.options.scales!.y!.max = Math.max(...allValues) + 1
|
||||
angleChart.update('none')
|
||||
}
|
||||
|
||||
if ($features.bmp) {
|
||||
updateChartData(tempChart, $imu.bmp_temp, 'Temperature')
|
||||
updateChartData(altitudeChart, $imu.altitude, 'Altitude')
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
socket.on(MessageTopic.imu, (data: IMU) => {
|
||||
console.log(data)
|
||||
imu.addData(data)
|
||||
})
|
||||
|
||||
initializeCharts()
|
||||
intervalId = setInterval(updateData, 200)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
socket.off(MessageTopic.imu)
|
||||
clearInterval(intervalId)
|
||||
})
|
||||
</script>
|
||||
|
||||
<SettingsCard collapsible={false}>
|
||||
{#snippet icon()}
|
||||
<Rotate3d class="flex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<span>IMU</span>
|
||||
{/snippet}
|
||||
{#snippet icon()}
|
||||
<Rotate3d class="flex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<span>IMU</span>
|
||||
{/snippet}
|
||||
|
||||
{#if $features.imu}
|
||||
<div class="w-full overflow-x-auto">
|
||||
<div
|
||||
class="flex w-full flex-col space-y-1 h-60"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}>
|
||||
<canvas bind:this={angleChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $features.imu}
|
||||
<div class="w-full overflow-x-auto">
|
||||
<div
|
||||
class="flex w-full flex-col space-y-1 h-60"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<canvas bind:this={angleChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $features.bmp}
|
||||
<div class="w-full overflow-x-auto">
|
||||
<div
|
||||
class="flex w-full flex-col space-y-1 h-60"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}>
|
||||
<canvas bind:this={tempChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full overflow-x-auto">
|
||||
<div
|
||||
class="flex w-full flex-col space-y-1 h-60"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}>
|
||||
<canvas bind:this={altitudeChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $features.bmp}
|
||||
<div class="w-full overflow-x-auto">
|
||||
<div
|
||||
class="flex w-full flex-col space-y-1 h-60"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<canvas bind:this={tempChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full overflow-x-auto">
|
||||
<div
|
||||
class="flex w-full flex-col space-y-1 h-60"
|
||||
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||
>
|
||||
<canvas bind:this={altitudeChartElement}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</SettingsCard>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import Servos from './servos.svelte'
|
||||
import ServoTable from './ServoTable.svelte'
|
||||
import Servos from './servos.svelte'
|
||||
import ServoTable from './ServoTable.svelte'
|
||||
|
||||
let servoId = $state(0)
|
||||
let pwm = $state(306)
|
||||
let servoId = $state(0)
|
||||
let pwm = $state(306)
|
||||
</script>
|
||||
|
||||
<div class="mx-0 my-1 flex flex-col space-y-4 sm:mx-8 sm:my-8">
|
||||
<Servos bind:servoId bind:pwm />
|
||||
<ServoTable {servoId} {pwm} />
|
||||
<Servos bind:servoId bind:pwm />
|
||||
<ServoTable {servoId} {pwm} />
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,113 +1,117 @@
|
||||
<script lang="ts">
|
||||
import { api } from '$lib/api'
|
||||
import { onMount } from 'svelte'
|
||||
import { RotateCw, RotateCcw } from '$lib/components/icons'
|
||||
interface Props {
|
||||
data?: any
|
||||
servoId?: number
|
||||
pwm?: number
|
||||
}
|
||||
|
||||
let {
|
||||
data = $bindable({
|
||||
servos: []
|
||||
}),
|
||||
pwm = $bindable(306),
|
||||
servoId = $bindable(0)
|
||||
}: Props = $props()
|
||||
|
||||
const updateValue = (event: Event, index: number, key: string) => {
|
||||
data.servos[index][key] = Number((event.target as HTMLInputElement).value)
|
||||
}
|
||||
|
||||
const syncConfig = async () => {
|
||||
await api.post('/api/servo/config', data)
|
||||
}
|
||||
|
||||
const toggleDirection = async (index: number) => {
|
||||
data.servos[index].direction = data.servos[index].direction === 1 ? -1 : 1
|
||||
await syncConfig()
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const result = await api.get('/api/servo/config')
|
||||
if (result.isOk()) {
|
||||
data = result.inner
|
||||
import { api } from '$lib/api'
|
||||
import { onMount } from 'svelte'
|
||||
import { RotateCw, RotateCcw } from '$lib/components/icons'
|
||||
interface Props {
|
||||
data?: any
|
||||
servoId?: number
|
||||
pwm?: number
|
||||
}
|
||||
})
|
||||
|
||||
const setCenterPWM = async () => {
|
||||
console.log('setCenterPWM', servoId, pwm)
|
||||
data.servos[servoId]['center_pwm'] = pwm
|
||||
await syncConfig()
|
||||
}
|
||||
let {
|
||||
data = $bindable({
|
||||
servos: []
|
||||
}),
|
||||
pwm = $bindable(306),
|
||||
servoId = $bindable(0)
|
||||
}: Props = $props()
|
||||
|
||||
const updateValue = (event: Event, index: number, key: string) => {
|
||||
data.servos[index][key] = Number((event.target as HTMLInputElement).value)
|
||||
}
|
||||
|
||||
const syncConfig = async () => {
|
||||
await api.post('/api/servo/config', data)
|
||||
}
|
||||
|
||||
const toggleDirection = async (index: number) => {
|
||||
data.servos[index].direction = data.servos[index].direction === 1 ? -1 : 1
|
||||
await syncConfig()
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const result = await api.get('/api/servo/config')
|
||||
if (result.isOk()) {
|
||||
data = result.inner
|
||||
}
|
||||
})
|
||||
|
||||
const setCenterPWM = async () => {
|
||||
console.log('setCenterPWM', servoId, pwm)
|
||||
data.servos[servoId]['center_pwm'] = pwm
|
||||
await syncConfig()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<button class="btn btn-sm btn-primary" onclick={() => setCenterPWM()}>Set center pwm</button>
|
||||
<button class="btn btn-sm btn-primary" onclick={() => setCenterPWM()}>Set center pwm</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Servo</th>
|
||||
<th>Center PWM</th>
|
||||
<th>Center Angle</th>
|
||||
<th>Direction</th>
|
||||
<th>Conversion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each data.servos as servo, index}
|
||||
<tr class="hover:bg-base-200">
|
||||
<td class="font-medium">Servo {index}</td>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
class="input input-sm input-bordered w-20"
|
||||
value={servo.center_pwm}
|
||||
onblur={syncConfig}
|
||||
oninput={event => updateValue(event, index, 'center_pwm')}
|
||||
min="80"
|
||||
max="600" />
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
step="0.1"
|
||||
class="input input-sm input-bordered w-20"
|
||||
value={servo.center_angle}
|
||||
onblur={syncConfig}
|
||||
oninput={event => updateValue(event, index, 'center_angle')}
|
||||
min="-90"
|
||||
max="90" />
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
class="btn btn-sm btn-ghost"
|
||||
title="Toggle direction {servo.direction}"
|
||||
onclick={() => toggleDirection(index)}>
|
||||
{#if servo.direction === 1}
|
||||
<RotateCw class="w-4 h-4 text-green-500" />
|
||||
{:else}
|
||||
<RotateCcw class="w-4 h-4" />
|
||||
{/if}
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
class="input input-sm input-bordered w-20"
|
||||
value={servo.conversion}
|
||||
onblur={syncConfig}
|
||||
oninput={event => updateValue(event, index, 'conversion')}
|
||||
min="0"
|
||||
max="10" />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table table-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Servo</th>
|
||||
<th>Center PWM</th>
|
||||
<th>Center Angle</th>
|
||||
<th>Direction</th>
|
||||
<th>Conversion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each data.servos as servo, index}
|
||||
<tr class="hover:bg-base-200">
|
||||
<td class="font-medium">Servo {index}</td>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
class="input input-sm input-bordered w-20"
|
||||
value={servo.center_pwm}
|
||||
onblur={syncConfig}
|
||||
oninput={event => updateValue(event, index, 'center_pwm')}
|
||||
min="80"
|
||||
max="600"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
step="0.1"
|
||||
class="input input-sm input-bordered w-20"
|
||||
value={servo.center_angle}
|
||||
onblur={syncConfig}
|
||||
oninput={event => updateValue(event, index, 'center_angle')}
|
||||
min="-90"
|
||||
max="90"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
class="btn btn-sm btn-ghost"
|
||||
title="Toggle direction {servo.direction}"
|
||||
onclick={() => toggleDirection(index)}
|
||||
>
|
||||
{#if servo.direction === 1}
|
||||
<RotateCw class="w-4 h-4 text-green-500" />
|
||||
{:else}
|
||||
<RotateCcw class="w-4 h-4" />
|
||||
{/if}
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
class="input input-sm input-bordered w-20"
|
||||
value={servo.conversion}
|
||||
onblur={syncConfig}
|
||||
oninput={event => updateValue(event, index, 'conversion')}
|
||||
min="0"
|
||||
max="10"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -1,64 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { socket } from '$lib/stores'
|
||||
import { MessageTopic } from '$lib/types/models'
|
||||
import { throttler as Throttler } from '$lib/utilities'
|
||||
import { socket } from '$lib/stores'
|
||||
import { MessageTopic } from '$lib/types/models'
|
||||
import { throttler as Throttler } from '$lib/utilities'
|
||||
|
||||
let { servoId = $bindable(0), pwm = $bindable(306) } = $props()
|
||||
let { servoId = $bindable(0), pwm = $bindable(306) } = $props()
|
||||
|
||||
let active = $state(false)
|
||||
let active = $state(false)
|
||||
|
||||
let allServos = $state(false)
|
||||
let allServos = $state(false)
|
||||
|
||||
const throttler = new Throttler()
|
||||
const throttler = new Throttler()
|
||||
|
||||
const activateServo = () => {
|
||||
socket.sendEvent(MessageTopic.servoState, { active: 1 })
|
||||
}
|
||||
const activateServo = () => {
|
||||
socket.sendEvent(MessageTopic.servoState, { active: 1 })
|
||||
}
|
||||
|
||||
const deactivateServo = () => {
|
||||
socket.sendEvent(MessageTopic.servoState, { active: 0 })
|
||||
}
|
||||
const deactivateServo = () => {
|
||||
socket.sendEvent(MessageTopic.servoState, { active: 0 })
|
||||
}
|
||||
|
||||
const updatePWM = () => {
|
||||
throttler.throttle(() => {
|
||||
socket.sendEvent(MessageTopic.servoPWM, { servo_id: servoId, pwm })
|
||||
}, 10)
|
||||
}
|
||||
const updatePWM = () => {
|
||||
throttler.throttle(() => {
|
||||
socket.sendEvent(MessageTopic.servoPWM, { servo_id: servoId, pwm })
|
||||
}, 10)
|
||||
}
|
||||
|
||||
const toggleMode = () => {
|
||||
servoId = allServos ? -1 : 0
|
||||
}
|
||||
const toggleMode = () => {
|
||||
servoId = allServos ? -1 : 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<h2 class="text-lg">General servo configuration</h2>
|
||||
<span>Servo</span>
|
||||
<span>{pwm}</span>
|
||||
<h2 class="text-lg">General servo configuration</h2>
|
||||
<span>Servo</span>
|
||||
<span>{pwm}</span>
|
||||
</div>
|
||||
<input
|
||||
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" />
|
||||
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"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<h2 class="text-lg">General servo configuration</h2>
|
||||
<span>
|
||||
<label for="mode">All servoes</label>
|
||||
<input type="checkbox" class="toggle" bind:checked={allServos} onchange={toggleMode} />
|
||||
</span>
|
||||
<span>
|
||||
<label for="active">Active</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle"
|
||||
bind:checked={active}
|
||||
onchange={active ? activateServo : deactivateServo} />
|
||||
</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<label for="servoId">Servo active {servoId}</label>
|
||||
<input type="range" min="0" max="11" step="1" bind:value={servoId} />
|
||||
</span>
|
||||
<h2 class="text-lg">General servo configuration</h2>
|
||||
<span>
|
||||
<label for="mode">All servoes</label>
|
||||
<input type="checkbox" class="toggle" bind:checked={allServos} onchange={toggleMode} />
|
||||
</span>
|
||||
<span>
|
||||
<label for="active">Active</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle"
|
||||
bind:checked={active}
|
||||
onchange={active ? activateServo : deactivateServo}
|
||||
/>
|
||||
</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<label for="servoId">Servo active {servoId}</label>
|
||||
<input type="range" min="0" max="11" step="1" bind:value={servoId} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user