Skip to content

Commit

Permalink
Merge pull request #44 from TrickfireRobotics/missioncontrol-addbases…
Browse files Browse the repository at this point in the history
…tationstates

Missioncontrol addbasestationstates
  • Loading branch information
Quashnock authored Jan 4, 2025
2 parents 6ada250 + 78357a4 commit 6a20151
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 66 deletions.
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
25 changes: 25 additions & 0 deletions src/store/operationStateStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineStore } from 'pinia';
import { createPublisher } from '@/lib/roslibUtils/createPublisher';
import { ref } from 'vue';

export type OperationState = 'disabled' | 'teleoperation' | 'autonomous';

export const useOperationStateStore = defineStore('operationType', () => {
const operationStatePublisher = createPublisher({
topicName: '/setOperationState',
topicType: 'std_msgs/String',
});
const operationState = ref<OperationState>('disabled');

/**
* This function sets the operation state to the specified state and sends out a message on the ros topic to change the state.
* @param state - the new operation state to set the driver to.
*/
function setOperationState(state: OperationState) {
operationState.value = state;
operationStatePublisher.publish({ data: operationState.value });
}

// Return all state, getters and functions
return { operationState, setOperationState };
});

0 comments on commit 6a20151

Please sign in to comment.