Skip to content

Commit

Permalink
ping-viewer-next-frontend: Add Ping1D widget
Browse files Browse the repository at this point in the history
  • Loading branch information
RaulTrombin committed Sep 25, 2024
1 parent ff02a6a commit 8e29caa
Show file tree
Hide file tree
Showing 5 changed files with 666 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@
<script setup>
import { ref } from "vue";
import WebsocketClient from "../widgets/WebsocketClient.vue";
import Ping1DWidget from "../widgets/sonar1d/Ping1D_loader.vue";
const selectedComponent = ref("WebsocketClient");
const isCollapsed = ref(false);
const components = {
WebsocketClient,
Ping1DWidget,
};
const toggleSidebar = () => {
Expand Down
150 changes: 150 additions & 0 deletions ping-viewer-next-frontend/src/components/widgets/sonar1d/Ping1D.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<template>
<div class="flex flex-col h-full bg-gray-900 p-4">
<WaterfallDisplay
:width="width"
:height="height"
:max-depth="maxDepth"
:min-depth="minDepth"
:column-count="columnCount"
:sensor-data="sensorData"
:color-palette="colorPalette"
:current-depth="currentDepth"
:accuracy="accuracy"
:confidence="confidence"
:tickCount="tickCount"
depth-line-color="yellow"
depth-text-color="yellow"
current-depth-color="yellow"
confidence-color="#00FF00"
text-background="rgba(0, 0, 0, 0.5)"
@update:columnCount="columnCount = $event"
/>
</div>
</template>

<script>
import { onUnmounted, reactive, ref, watch } from "vue";
import WaterfallDisplay from "./WaterfallDisplay.vue";
export default {
components: {
WaterfallDisplay,
},
props: {
websocketUrl: {
type: String,
required: true,
},
colorPalette: {
type: String,
required: true,
},
isConnected: {
type: Boolean,
required: true,
},
},
emits: ["update:lastUpdateTime", "update:isConnected"],
setup(props, { emit }) {
const columnCount = ref(226);
const sensorData = reactive([]);
const currentDepth = ref(0);
const minDepth = ref(0);
const maxDepth = ref(0);
const confidence = ref(0);
const accuracy = ref(0);
let socket = null;
watch(
() => props.isConnected,
(newIsConnected) => {
if (newIsConnected) {
connectWebSocket();
} else {
disconnectWebSocket();
}
},
);
function connectWebSocket() {
if (socket) return;
socket = new WebSocket(props.websocketUrl);
socket.onopen = () => {
console.log("WebSocket connected");
emit("update:isConnected", true);
};
socket.onmessage = (event) => {
try {
const parsedData = JSON.parse(event.data);
const profile = parsedData.DeviceMessage.PingMessage.Ping1D.Profile;
sensorData.splice(0, sensorData.length, ...profile.profile_data);
currentDepth.value = profile.distance / 1000;
minDepth.value = profile.scan_start / 1000;
maxDepth.value = profile.scan_length / 1000;
confidence.value = profile.confidence;
accuracy.value =
((100 - confidence.value) / 100) *
(maxDepth.value - minDepth.value) *
0.1;
emit("update:lastUpdateTime", new Date().toLocaleTimeString());
console.log(
"Received new sensor data:",
profile.profile_data.length,
"values, current depth:",
currentDepth.value.toFixed(2),
"m, min depth:",
minDepth.value.toFixed(2),
"m, max depth:",
maxDepth.value.toFixed(2),
"m, confidence:",
confidence.value,
"%, accuracy:",
accuracy.value.toFixed(2),
"m",
);
} catch (error) {
console.error("Error parsing WebSocket data:", error);
}
};
socket.onerror = (error) => {
console.error("WebSocket error:", error);
};
socket.onclose = () => {
console.log("WebSocket disconnected");
emit("update:isConnected", false);
socket = null;
};
}
function disconnectWebSocket() {
if (socket) {
socket.close();
socket = null;
}
}
onUnmounted(() => {
disconnectWebSocket();
});
return {
columnCount,
sensorData,
currentDepth,
minDepth,
maxDepth,
confidence,
accuracy,
};
},
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<template>
<div class="flex flex-col h-full bg-gray-900 text-white overflow-hidden">
<div class="flex-1 p-4 overflow-y-auto flex flex-col">
<h2 class="text-2xl font-bold text-white mb-4">Ping1D Waterfall Display</h2>

<div class="mb-4">
<label for="websocketUrl" class="block text-sm font-medium text-gray-300">WebSocket URL:</label>
<input
id="websocketUrl"
v-model="websocketUrl"
type="text"
class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md bg-gray-700 text-white"
placeholder="Enter WebSocket URL"
/>
</div>

<div class="mb-4 flex items-center justify-between">
<div class="w-1/2 mr-2">
<label for="colorPalette" class="block text-sm font-medium text-gray-300">Color Palette:</label>
<select
id="colorPalette"
v-model="selectedColorPalette"
class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md bg-gray-700 text-white"
>
<option v-for="palette in colorPalettes" :key="palette.value" :value="palette.value">
{{ palette.label }}
</option>
</select>
</div>
<div class="w-1/2 ml-2">
<button @click="toggleWebSocket" class="w-full px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors">
{{ isConnected ? 'Disconnect' : 'Connect' }} WebSocket
</button>
</div>
</div>

<div class="mb-4 text-white">
Last update: {{ lastUpdateTime }}
</div>

<div class="flex-1 min-h-[100px] overflow-hidden">
<Ping1D
:websocket-url="websocketUrl"
:color-palette="selectedColorPalette"
:is-connected="isConnected"
@update:lastUpdateTime="lastUpdateTime = $event"
@update:isConnected="isConnected = $event"
/>
</div>
</div>
</div>
</template>

<script>
import { onMounted, ref } from "vue";
import Ping1D from "./Ping1D.vue";
export default {
components: {
Ping1D,
},
setup() {
const websocketUrl = ref("");
onMounted(() => {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const host = window.location.host;
websocketUrl.value = `${protocol}//${host}/ws?device_number=00000000-0000-0000-1cc4-7b702224f52d`;
});
const colorPalettes = [
{ value: "transparent", label: "Transparent" },
{ value: "heatmap", label: "Heatmap" },
{ value: "grayscale", label: "Grayscale" },
{ value: "ocean", label: "Ocean" },
];
const selectedColorPalette = ref("transparent");
const isConnected = ref(false);
const lastUpdateTime = ref("Not updated yet");
function toggleWebSocket() {
if (isConnected.value) {
isConnected.value = false;
} else {
if (websocketUrl.value.trim() !== "") {
isConnected.value = true;
} else {
alert("Please enter a valid WebSocket URL");
}
}
}
return {
websocketUrl,
colorPalettes,
selectedColorPalette,
isConnected,
lastUpdateTime,
toggleWebSocket,
};
},
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<template>
<div class="waterfall-display relative w-full h-full">
<WaterfallShader
:width="width"
:height="height"
:max-depth="maxDepth"
:min-depth="minDepth"
:column-count="columnCount"
:sensor-data="sensorData"
:color-palette="colorPalette"
@update:columnCount="$emit('update:columnCount', $event)"
/>
<div class="depth-line absolute top-0 right-0 h-full w-px" :style="{ backgroundColor: depthLineColor }">
<div
v-for="tick in depthTicks"
:key="tick"
class="tick absolute right-0 w-2 h-px"
:style="{ top: `${tickPosition(tick)}%`, backgroundColor: depthLineColor }"
>
<span
class="absolute right-3 transform -translate-y-1/2 text-xs px-1 rounded"
:style="{ color: depthTextColor, backgroundColor: textBackground }"
>
{{ tick.toFixed(1) }}m
</span>
</div>
</div>
<div
class="depth-arrow absolute right-0 w-0 h-0 border-solid border-transparent border-l-8"
:style="{ top: `${arrowPosition}%`, borderLeftColor: depthArrowColor, transform: 'translateY(-50%)' }"
></div>
<div
class="current-depth absolute top-2 left-2 text-sm px-1 rounded"
:style="{ color: currentDepthColor, backgroundColor: textBackground }"
>
Depth: {{ currentDepth.toFixed(2) }}m ± {{ accuracy.toFixed(2) }}m
</div>
<div
class="confidence absolute top-8 left-2 text-sm px-1 rounded"
:style="{ color: confidenceColor, backgroundColor: textBackground }"
>
Confidence: {{ confidence }}%
</div>
</div>
</template>

<script>
import { computed } from "vue";
import WaterfallShader from "./WaterfallShader.vue";
export default {
name: "WaterfallDisplay",
components: {
WaterfallShader,
},
props: {
width: { type: Number, default: 500 },
height: { type: Number, default: 400 },
maxDepth: { type: Number, default: 200 },
minDepth: { type: Number, default: 0 },
columnCount: { type: Number, default: 200 },
sensorData: { type: Array, default: () => [] },
colorPalette: { type: String, default: "ocean" },
currentDepth: { type: Number, default: 0 },
accuracy: { type: Number, default: 0 },
confidence: { type: Number, default: 0 },
depthLineColor: { type: String, default: "#FFFFFF" },
depthTextColor: { type: String, default: "#FFFFFF" },
currentDepthColor: { type: String, default: "#FFFF00" },
confidenceColor: { type: String, default: "#00FF00" },
textBackground: { type: String, default: "rgba(0, 0, 0, 0.5)" },
depthArrowColor: { type: String, default: "#FF0000" },
tickCount: { type: Number, default: 5 },
},
emits: ["update:columnCount"],
setup(props) {
const depthTicks = computed(() => {
const depthRange = props.maxDepth - props.minDepth;
return Array.from(
{ length: props.tickCount },
(_, i) => props.minDepth + (i / (props.tickCount - 1)) * depthRange
);
});
const arrowPosition = computed(() => {
const depthRange = props.maxDepth - props.minDepth;
const relativeDepth = props.currentDepth - props.minDepth;
return (relativeDepth / depthRange) * 100;
});
const tickPosition = (depth) => {
const depthRange = props.maxDepth - props.minDepth;
const relativeDepth = depth - props.minDepth;
return (relativeDepth / depthRange) * 100;
};
return {
depthTicks,
arrowPosition,
tickPosition,
};
},
};
</script>
Loading

0 comments on commit 8e29caa

Please sign in to comment.