Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…-control into teja-dasari/map-development
  • Loading branch information
O-xix committed Jan 31, 2025
2 parents 6193e2b + 6c8f033 commit a4a6de2
Show file tree
Hide file tree
Showing 10 changed files with 1,702 additions and 174 deletions.
1,461 changes: 1,416 additions & 45 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"scripts": {
"dev": "vite",
"dev-tools": "cross-env USE_VITE_DEVTOOLS=true vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
Expand Down Expand Up @@ -32,6 +33,7 @@
"@types/three": "^0.170.0",
"@vitejs/plugin-vue": "^4.6.2",
"@vue/tsconfig": "^0.1.3",
"cross-env": "^7.0.3",
"eslint": "^9.14.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.30.0",
Expand All @@ -42,6 +44,7 @@
"typescript": "~4.8.4",
"typescript-eslint": "^8.12.2",
"vite": "^4.5.5",
"vite-plugin-vue-devtools": "^7.6.7",
"vue-tsc": "^1.8.27",
"webpack": "^5.95.0"
}
Expand Down
222 changes: 156 additions & 66 deletions src/components/Navbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import PowerPlugIcon from 'vue-material-design-icons/PowerPlug.vue';
import ControllerIcon from 'vue-material-design-icons/ControllerClassic.vue';
import { useRoslibStore } from '@/store/roslibStore';
import { useControllerStore } from '@/store/controllerStore';
import { useOperationStateStore } from '../store/operationStateStore';
//TODO implement latency
const roslib = useRoslibStore();
const controller = useControllerStore();
const operation = useOperationStateStore();
const currentTab = ref(0);
const setCurrentTab = (newValue: number) => {
currentTab.value = newValue;
Expand Down Expand Up @@ -80,62 +82,94 @@ const pageIconArr: PageIcon = [
</script>
<template>
<nav>
<img id="logo" src="../assets/trickfire_logo_transparent.png" alt="Trickfire logo" />
<h1 id="logo-text">Mission Control</h1>
<RouterLink
v-for="(pageIcon, index) in pageIconArr"
:key="index"
:to="pageIcon.label"
class="container navbar-tab"
:class="{ 'current-page': currentTab === index }"
@click="setCurrentTab(index)"
>
<h4>{{ pageIcon.label }}</h4>
<component :is="pageIcon.icon" class="page-icon" :title="pageIcon.helperText" />
</RouterLink>
<div
class="container indicator-container"
:title="`Websocket: ${roslib.isWebSocketConnected ? `Connected` : `Disconnected`}`"
>
<h4 id="status">WS</h4>
<component
:is="PowerPlugIcon"
class="page-icon"
:class="{ green: roslib.isWebSocketConnected, red: !roslib.isWebSocketConnected }"
/>
</div>
<div
class="container indicator-container"
:title="`Camera: ${roslib.isWebSocketConnected ? `Connected` : `Disconnected`}`"
>
<h4 id="status">CAM</h4>
<component
:is="CameraIcon"
class="page-icon"
:class="{ green: roslib.isWebSocketConnected, red: !roslib.isWebSocketConnected }"
/>
</div>
<div
class="container indicator-container"
:title="`Controller: ${controller.isGamepadConnected ? `Connected` : `Disconnected`}`"
>
<h4 id="status">CTRL</h4>
<component
:is="ControllerIcon"
class="page-icon"
:class="{ green: controller.isGamepadConnected, red: !controller.isGamepadConnected }"
/>
</div>
<div class="container indicator-container">
<h4 id="status">Ping</h4>
<h4>{{ roslib.latency + 'ms' }}</h4>
</div>
<section id="logo-section">
<img id="logo" src="../assets/trickfire_logo_transparent.png" alt="Trickfire logo" />
<h1 id="logo-text">Mission Control</h1>
</section>
<section id="page-section">
<RouterLink
v-for="(pageIcon, index) in pageIconArr"
:key="index"
:to="pageIcon.label"
class="container navbar-tab"
:class="{ 'current-page': currentTab === index }"
@click="setCurrentTab(index)"
>
<h4>{{ pageIcon.label }}</h4>
<component :is="pageIcon.icon" class="page-icon" :title="pageIcon.helperText" />
</RouterLink>
</section>
<section id="states-section">
<div id="operation-selector" class="container">
<button
id="disable-button"
title="Disabled"
:class="{ checked: operation.operationState === 'disabled' }"
@click="operation.setOperationState('disabled')"
>
Disable
</button>
<button
id="teleoperation-button"
title="TeleOperation"
:class="{ checked: operation.operationState === 'teleoperation' }"
@click="operation.setOperationState('teleoperation')"
>
TeleOp
</button>
<button
id="autonomous-button"
title="Autonomous"
:class="{ checked: operation.operationState === 'autonomous' }"
@click="operation.setOperationState('autonomous')"
>
Auto
</button>
</div>
<div
class="container"
:title="`Websocket: ${roslib.isWebSocketConnected ? `Connected` : `Disconnected`}`"
>
<h4 id="status">WS</h4>
<component
:is="PowerPlugIcon"
class="page-icon"
:class="{ green: roslib.isWebSocketConnected, red: !roslib.isWebSocketConnected }"
/>
</div>
<div
class="container"
:title="`Camera: ${roslib.isWebSocketConnected ? `Connected` : `Disconnected`}`"
>
<h4 id="status">CAM</h4>
<component
:is="CameraIcon"
class="page-icon"
:class="{ green: roslib.isWebSocketConnected, red: !roslib.isWebSocketConnected }"
/>
</div>
<div
class="container"
:title="`Controller: ${controller.isGamepadConnected ? `Connected` : `Disconnected`}`"
>
<h4 id="status">CTRL</h4>
<component
:is="ControllerIcon"
class="page-icon"
:class="{ green: controller.isGamepadConnected, red: !controller.isGamepadConnected }"
/>
</div>
<div class="container">
<h4 id="status">Ping</h4>
<h4>{{ roslib.latency + 'ms' }}</h4>
</div>
</section>
</nav>
</template>

<style lang="scss" scoped>
nav {
padding: 0rem 1rem;
padding: 0 0 0 1rem;
grid-area: nav;
display: flex;
align-items: center;
Expand All @@ -145,42 +179,98 @@ nav {
h2,
h3,
h4,
p {
p,
select {
color: var(--white);
white-space: nowrap;
overflow: hidden;
}
.page-icon {
transform: scale(1.25);
}
.current-page {
background-color: var(--light-grey);
}
.navbar-tab {
padding: 0 0.3rem;
min-width: 4rem;
min-width: 4.5rem;
.page-icon {
transform: scale(1.25);
}
}
.navbar-tab:not(.current-page):hover {
background-color: hsl(0, 0%, 16%);
}
#logo {
max-width: 100%;
max-height: 4rem;
}
#logo-text {
margin: 0 1rem;
}
.container {
height: var(--nav-bar-size);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.indicator-container {
min-width: max-content;
padding: 0 1.5rem;
#logo-section {
display: flex;
align-items: center;
flex-shrink: 0;
#logo {
max-width: 100%;
max-height: 3rem;
}
#logo-text {
margin: 0 1rem;
}
}
#page-section {
display: flex;
overflow-x: scroll;
overflow-y: hidden;
scrollbar-width: none;
flex-grow: 1;
border-right: 1px solid var(--white);
border-left: 1px solid var(--white);
}
#states-section {
display: flex;
gap: 1.75rem;
height: var(--nav-bar-size);
padding: 0 2rem 0 1.5rem;
background-color: hsl(240, 20%, 20%);
#operation-selector {
margin: auto 0;
display: flex;
flex-direction: row;
padding: 0 0.5rem;
background-color: hsl(240, 20%, 10%);
border-radius: 4px;
height: 80%;
gap: 0.25rem;
button {
height: 80%;
cursor: pointer;
align-items: center;
padding: 0 1rem;
border-radius: 4px;
background-color: transparent;
color: white;
&.checked {
background-image: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
&#disable-button.checked {
background-color: hsl(0, 100%, 27%);
}
&#autonomous-button.checked {
background-color: hsl(300, 100%, 23%);
}
&#teleoperation-button.checked {
background-color: hsl(120, 100%, 15%);
}
}
}
}
.red {
color: var(--error);
Expand Down
39 changes: 29 additions & 10 deletions src/components/camera/CameraModule.vue
Original file line number Diff line number Diff line change
@@ -1,35 +1,54 @@
<script setup lang="ts">
import type { Subscriber } from '@/lib/roslibUtils/createSubscriber';
import { createSubscriber } from '@/lib/roslibUtils/createSubscriber';
import { onMounted, ref } from 'vue';
import type { CompressedImage } from '@/lib/roslibUtils/rosTypes';
export interface CameraModuleProps {
cameraSub: Subscriber<'sensor_msgs/msg/CompressedImage', true>;
cameraId: number;
cameraName: string;
cameraUrl: string;
}
const props = defineProps<CameraModuleProps>();
const blackImage =
'data:image/jpg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8AKp//2Q==';
const cameraSub = createSubscriber({
topicName: `video_frames${props.cameraId}`,
topicType: 'sensor_msgs/msg/CompressedImage',
});
const cameraSubURL = ref<string>(blackImage);
function cameraCallback(message: CompressedImage) {
const blob = new Blob([message.data], { type: 'image/' + message.format });
cameraSubURL.value = URL.createObjectURL(blob);
}
const onAndOffHandler = () => {
if (props.cameraSub.isOn.value) {
props.cameraSub.stop();
if (cameraSub.isOn.value) {
cameraSub.stop();
} else {
props.cameraSub.start();
cameraSub.start({ callback: cameraCallback });
}
};
onMounted(() => {
cameraSub.start({ callback: cameraCallback });
});
</script>

<template>
<div>
<h3 class="camera-name">{{ props.cameraName }}</h3>
<h3 class="camera-fps">FPS:</h3>
<img :alt="cameraName" :src="props.cameraUrl" />
<img :alt="cameraName" :src="cameraSubURL" />
<button
:class="{
'button-toggle--off': !props.cameraSub.isOn.value,
'button-toggle--on': props.cameraSub.isOn.value,
'button-toggle--off': !cameraSub.isOn.value,
'button-toggle--on': cameraSub.isOn.value,
}"
@click="onAndOffHandler"
>
{{ props.cameraSub.isOn.value ? 'On' : 'Off' }}
{{ cameraSub.isOn.value ? 'On' : 'Off' }}
</button>
</div>
</template>
Expand Down
12 changes: 3 additions & 9 deletions src/lib/roslibUtils/createPublisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,22 @@ export function createPublisher<T extends TopicType>(options: {
topicType: T;
}): Publisher<T> {
const ros = useRoslibStore();
return createPublisherForRos(ros.ros, options);
return createPublisherForRos(ros.getTopic, options);
}

/**
* Equivalent to createPublisher, except using a supplied roslib instead of the
* default one.
*/
export function createPublisherForRos<T extends TopicType>(
ros: ROSLIB.Ros,
getTopic: <T>(name: string, type: string) => ROSLIB.Topic<T>,
options: {
topicName: string;
topicType: T;
},
): Publisher<T> {
const { topicName, topicType } = options;
const topic = new ROSLIB.Topic<TopicTypeMap[T]>({
ros,
name: topicName,
messageType: topicType,
compression: 'cbor',
reconnect_on_close: true,
});
const topic = getTopic<TopicTypeMap[T]>(topicName, topicType);

const publish: Publisher<T>['publish'] = (data, options) => {
const { isDebugging } = options || {};
Expand Down
Loading

0 comments on commit a4a6de2

Please sign in to comment.