Skip to content

Commit

Permalink
Merge pull request #131 from tritonuas/feat/bottleConnection
Browse files Browse the repository at this point in the history
Bottle Connection Status
  • Loading branch information
Tyler-Lentz authored Jun 12, 2024
2 parents e0b96bd + 87b8a7b commit 8fdf0a1
Show file tree
Hide file tree
Showing 12 changed files with 215 additions and 28 deletions.
Binary file added houston/src/assets/coca.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added houston/src/assets/fanta.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added houston/src/assets/sunglass_emoji.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions houston/src/components/BottleConnectionStatus.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.flex-container {
display: flex;
flex-flow: row wrap;
overflow-y: auto;
justify-content: space-evenly;
max-height: 150px;
max-width: 400px;
gap:10px;
}

.bottle_connection_status_title {
font-size: 35px;
}

.bottle_connection_status_bottle_number {
font-size: larger;
font-weight: bolder;
color:azure;
}
102 changes: 102 additions & 0 deletions houston/src/components/BottleConnectionStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import "./BottleConnectionStatus.css"

import MyModal from "./MyModal";
import { OBCConnInfo } from '../protos/obc.pb';
import { useEffect, useState } from "react";

import coca from "../assets/coca.png";
import sunglass_emoji from "../assets/sunglass_emoji.jpg"

interface props {
modalVisible: boolean;
closeModal: () => void;
}

/**
* A modal that displays coca-cola icons to represent disconnected bottles, along
* with different background colors to represent the severity.
* @param props - The properties of BottleConnectionStatus.
* @param props.modalVisible - A boolean that dictates the visibility of the BottleConnectionStatus.
* @param props.closeModal - A void function to close the BottleConnectionStatus.
* @returns The BottleConnectionStatus modal.
*/
function BottleConnectionStatus({modalVisible, closeModal}:props) {
const [obcStatus, setOBCStatus] = useState<OBCConnInfo>(JSON.parse(localStorage.getItem("obc_conn_status") || "{}") as OBCConnInfo);
const [modalType, setModalType] = useState('default')
const [droppedBottles, setDroppedBottles] = useState(0)

/**
* Note: the way protobuf serialization works is that if things are null values (false, 0.0) then they
* wont show up in the serialization, as that can be "implied" to be a zero value by it not being there.
* (At least this is my understanding). Therefore, if some of the expected values in the struct aren't there
* it is because they are false/0.0 or some other 0-like value.
*/

useEffect(() => {
setInterval(() => {
const data = localStorage.getItem("obc_conn_status") || "{}";
setOBCStatus(JSON.parse(data));
}, 1000);
}, []);

useEffect(() => {
('droppedBottleIdx' in obcStatus)
?
setDroppedBottles(obcStatus.droppedBottleIdx.length)
:
setDroppedBottles(0);

switch(droppedBottles) {
case 1:
case 2:
case 3:
case 4:
setModalType('warning');
break;
case 5:
setModalType('error');
break;
default:
setModalType('dafault');
break;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
},[obcStatus])

return (
<MyModal modalVisible={modalVisible} closeModal={closeModal} type={modalType}>
<div>
<div className="bottle_connection_status_title">
{droppedBottles==0
?
'All bottles online'
:
'Bottles that have lost connection'
}
</div>
{droppedBottles==0 ? <img src={sunglass_emoji} alt="Sunglasses Emoji" width={'368px'} height={'270px'}/> : null}
<ol className="flex-container">
{
('droppedBottleIdx' in obcStatus)
?
obcStatus.droppedBottleIdx.map((bottle, index) => {
return (
<div key={index} >
<img
src={coca}
alt="coca-cola can"
style={{ width: "30px", height: "50px", display: "inline-block"}}
/>
<div className="bottle_connection_status_bottle_number">{bottle}</div>
</div>
)
})
:
null
}
</ol>
</div>
</MyModal>
)
}
export default BottleConnectionStatus
12 changes: 7 additions & 5 deletions houston/src/components/MyModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ interface Props {
function backgroundColorPicker(type:string){
switch(type) {
case 'error':
return '#AC3344';
return '#AC3344';
case 'warning':
return '#FFBF00';
default:
return '#2C6CFB';
return '#2C6CFB';
}
}

Expand All @@ -44,7 +46,8 @@ function MyModal({children, modalVisible, closeModal, type="default", loading=fa
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
minWidth: 400,
maxWidth: '100%',
bgcolor: backgroundColor,
border: '2px solid #000',
borderRadius: "7px",
Expand All @@ -66,9 +69,8 @@ function MyModal({children, modalVisible, closeModal, type="default", loading=fa
<img src={exit} />
</Button>
<Typography id="modal-modal-title" variant="h6" component="h2" textAlign={"center"}>
{loading ? <div className="lds-dual-ring"></div> : null}
{loading ? <div className="lds-dual-ring"></div> : children}
</Typography>
{loading ? null : children}
</Box>
</Modal>
)
Expand Down
6 changes: 3 additions & 3 deletions houston/src/components/TakeoffSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { useMyModal } from "./UseMyModal";
import "./TakeoffSelector.css"


interface props{
interface props {
modalVisible: boolean;
closeModal: ( ) => void;
closeModal: () => void;
}

/**
Expand All @@ -18,7 +18,7 @@ interface props{
* @param props.closeModal - A void function to close the PlanePicker.
* @returns A modal with two options.
*/
function TakeoffSelector({modalVisible, closeModal}:props){
function TakeoffSelector({modalVisible, closeModal}:props) {

const {modalVisible: fetchModalVisible, openModal:fetchOpenModal, closeModal:fetchCloseModal} = useMyModal();
const [selectedTakeoff, setSelectedTakeoff] = useState(0);
Expand Down
2 changes: 1 addition & 1 deletion houston/src/pages/Control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ function Control({settings, planeCoordinates}:{settings: SettingsConfig, planeCo
window.addEventListener("storage", () => {handleStorageChange()})
window.dispatchEvent(new Event("storage"))
return () => {window.removeEventListener("storage", () => {handleStorageChange()})}
}, []);
});

useEffect(() => {
pullFlightBounds(setFlightBound, setSearchBound, setWayPoint);
Expand Down
10 changes: 5 additions & 5 deletions houston/src/pages/Drop.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { ChangeEvent, useState } from "react"
import { BottleDropIndex, BottleSwap } from "../protos/obc.pb";
import video from "../assets/IAMTHEANGRYPUMPKIN.mp4"
// import video from "../assets/IAMTHEANGRYPUMPKIN.mp4"

/**
* Page that lets the user perform a manual drop
* @returns manual drop page
*/
function Drop() {
const [bottle, setBottle] = useState<BottleDropIndex>(BottleDropIndex.A);
const [playing, setPlaying] = useState<boolean>(false);
// const [playing, setPlaying] = useState<boolean>(false);

const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
Expand Down Expand Up @@ -41,7 +41,7 @@ function Drop() {

const video = document.getElementById('pumpkin') as HTMLVideoElement;
video.play();
setPlaying(true);
// setPlaying(true);

fetch("/api/plane/dodropnow", {
method: "POST",
Expand All @@ -51,7 +51,7 @@ function Drop() {
.then(resp => {
console.log(resp)
video.play();
setPlaying(true);
// setPlaying(true);
})
}

Expand All @@ -62,7 +62,7 @@ function Drop() {
<input type="number" onChange={handleChange} value={bottle} />
<input type="button" value={`Drop Bottle ${bottle}`} onClick={handleDropClick}/>
</form>
<video src={video} id="pumpkin" style={{display: (!playing) ? "none" : ""}}></video>
{/* <video src={video} id="pumpkin" style={{display: (!playing) ? "none" : ""}}></video> */}
</>

);
Expand Down
86 changes: 73 additions & 13 deletions houston/src/pages/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { Outlet, NavLink, useNavigate } from "react-router-dom";
import "./Layout.css";
import csv from "../assets/csv.svg";
import duck from "../assets/duck.png"
import settingsIcon from "../assets/settings.svg"

import {getIconFromStatus, } from "../utilities/connection"
import {ConnectionStatus, } from "../utilities/temp"
import { Button, Typography } from "@mui/material";
import { useEffect, useState } from "react";
import MyModal from "../components/MyModal";
import { useMyModal } from "../components/UseMyModal";
import { OBCConnInfo } from '../protos/obc.pb';
import MyModal from "../components/MyModal";
import PlanePicker from "../components/PlanePicker";
import BottleConnectionStatus from "../components/BottleConnectionStatus";

import csv from "../assets/csv.svg";
import duck from "../assets/duck.png";
import settingsIcon from "../assets/settings.svg"
import sprite from "../assets/sprite.png";
import fanta from "../assets/fanta.png";
import coca from "../assets/coca.png";


/**
Expand All @@ -24,11 +31,10 @@ function Layout({statuses}:{statuses:ConnectionStatus[]}) {
const [influxReturnValue, setInfluxReturnValue] = useState('');
const {modalVisible, openModal, closeModal} = useMyModal();
const {modalVisible: planePickerModalVisible, openModal: planePickerOpenModal, closeModal: planePickerCloseModal} = useMyModal();
const handlePlanePickerModal = () => {
planePickerModalVisible ? planePickerCloseModal() : planePickerOpenModal();
}
const {modalVisible: bcsModalVisible, openModal: bcsOpenModal, closeModal:bcsCloseModal} = useMyModal();
const [icon, setIcon] = useState(localStorage.getItem("icon") || duck);

const [obcStatus, setOBCStatus] = useState<OBCConnInfo>(JSON.parse(localStorage.getItem("obc_conn_status") || "{}") as OBCConnInfo);
const [bottleIcon, setBottleIcon] = useState(sprite);
const checkForActive = ({isActive}:{isActive:boolean}) => {
if (isActive) {
return "active";
Expand All @@ -42,11 +48,13 @@ function Layout({statuses}:{statuses:ConnectionStatus[]}) {
data ? setIcon(data) : setIcon(duck);
};

useEffect(() => {
window.addEventListener("storage", () => {handleStorageChange()})
window.dispatchEvent(new Event("storage"))
return () => {window.removeEventListener("storage", () => {handleStorageChange()})}
}, []);
const handlePlanePickerModal = () => {
planePickerModalVisible ? planePickerCloseModal() : planePickerOpenModal();
}

const handleBottleConnectionStatusModal = () => {
bcsModalVisible ? bcsCloseModal() : bcsOpenModal();
}

const handleInflux = () => {
setLoading(true);
Expand All @@ -63,6 +71,50 @@ function Layout({statuses}:{statuses:ConnectionStatus[]}) {
});
}

useEffect(() => {
window.addEventListener("storage", () => {handleStorageChange()})
window.dispatchEvent(new Event("storage"))
return () => {window.removeEventListener("storage", () => {handleStorageChange()})}
});

/**
* Note: the way protobuf serialization works is that if things are null values (false, 0.0) then they
* wont show up in the serialization, as that can be "implied" to be a zero value by it not being there.
* (At least this is my understanding). Therefore, if some of the expected values in the struct aren't there
* it is because they are false/0.0 or some other 0-like value.
*/

useEffect(() => {
setInterval(() => {
const data = localStorage.getItem("obc_conn_status") || "{}";
setOBCStatus(JSON.parse(data));
}, 1000);
}, []);

useEffect(() => {
let droppedBottles;
('droppedBottleIdx' in obcStatus)
?
droppedBottles = obcStatus.droppedBottleIdx.length
:
droppedBottles = 0;

switch(droppedBottles) {
case 1:
case 2:
case 3:
case 4:
setBottleIcon(fanta);
break;
case 5:
setBottleIcon(coca);
break;
default:
setBottleIcon(sprite);
break;
}
},[obcStatus])

return (
<>
<nav className="topbar">
Expand All @@ -84,6 +136,14 @@ function Layout({statuses}:{statuses:ConnectionStatus[]}) {
<li>
<NavLink to="/drop" className={checkForActive}>Drop</NavLink>
</li>
<Button onClick={handleBottleConnectionStatusModal}>
<img
src={bottleIcon}
alt="bottle connection status"
style={{ width: bottleIcon == fanta ? "70px" : "30px", height: "50px", display: "inline-block"}}
/>
</Button>
<BottleConnectionStatus modalVisible={bcsModalVisible} closeModal={bcsCloseModal}></BottleConnectionStatus>
<Button onClick={openSettings}>
<img
src={settingsIcon}
Expand Down
1 change: 1 addition & 0 deletions internal/obc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ func (client *Client) DoDropNow(bottle *protos.BottleSwap) ([]byte, int) {
return body, httpErr.Status
}

// Tell the OBC to take a picture on the camera
func (client *Client) DoCameraCapture() ([]byte, int) {
body, httpErr := client.httpClient.Get("/camera/capture")
return body, httpErr.Status
Expand Down
5 changes: 4 additions & 1 deletion internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,10 @@ func (server *Server) postMatchedTargets() gin.HandlerFunc {
}

var data []byte
json.Unmarshal(data, &matchedTargets)
err = json.Unmarshal(data, &matchedTargets)
if err != nil {
println(err.Error())
}

body, code := server.obcClient.PostTargetMatchOverride(data)
if code != http.StatusOK {
Expand Down

0 comments on commit 8fdf0a1

Please sign in to comment.