diff --git a/houston/package-lock.json b/houston/package-lock.json index f477a98b..01d36d5b 100644 --- a/houston/package-lock.json +++ b/houston/package-lock.json @@ -22,8 +22,8 @@ }, "devDependencies": { "@types/leaflet": "^1.9.7", - "@types/react": "^18.2.15", - "@types/react-dom": "^18.2.7", + "@types/react": "^18.2.34", + "@types/react-dom": "^18.2.14", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "@vitejs/plugin-react-swc": "^3.3.2", @@ -1568,9 +1568,9 @@ "integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==" }, "node_modules/@types/react": { - "version": "18.2.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.28.tgz", - "integrity": "sha512-ad4aa/RaaJS3hyGz0BGegdnSRXQBkd1CCYDCdNjBPg90UUpLgo+WlJqb9fMYUxtehmzF3PJaTWqRZjko6BRzBg==", + "version": "18.2.37", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.37.tgz", + "integrity": "sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1578,9 +1578,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.13.tgz", - "integrity": "sha512-eJIUv7rPP+EC45uNYp/ThhSpE16k22VJUknt5OLoH9tbXoi8bMhwLf5xRuWMywamNbWzhrSmU7IBJfPup1+3fw==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.14.tgz", + "integrity": "sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==", "dev": true, "dependencies": { "@types/react": "*" @@ -5629,9 +5629,9 @@ } }, "node_modules/react-tooltip": { - "version": "5.21.5", - "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.21.5.tgz", - "integrity": "sha512-ey70qf6pBGi4U6xpyNlZAHobAhlo2dfxmImR2Bzd/DbLTsAYWz3TEaK+RMFuUZMq6hSPRbUHQSkP2rHBq4uFVg==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.22.0.tgz", + "integrity": "sha512-xbJBRY1LyHYd7j00UeBOqZR9SH/1S47Pe+m8vM1a+ZXglkeSNnBt5YYoPttU/amjC/VZJAPQ8+2B9x8Fl8U1qA==", "dependencies": { "@floating-ui/dom": "^1.0.0", "classnames": "^2.3.0" diff --git a/houston/package.json b/houston/package.json index ffc891a8..ad5dc10f 100644 --- a/houston/package.json +++ b/houston/package.json @@ -26,8 +26,8 @@ }, "devDependencies": { "@types/leaflet": "^1.9.7", - "@types/react": "^18.2.15", - "@types/react-dom": "^18.2.7", + "@types/react": "^18.2.34", + "@types/react-dom": "^18.2.14", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "@vitejs/plugin-react-swc": "^3.3.2", diff --git a/houston/src/pages/Control.css b/houston/src/pages/Control.css new file mode 100644 index 00000000..458e0045 --- /dev/null +++ b/houston/src/pages/Control.css @@ -0,0 +1,142 @@ +.controls-page { + display: flex; + flex: 1; + flex-direction: row; + justify-content: center; + align-items: center; + border: 1px solid black; + padding: 50px; + padding-left: 100px; + padding-right: 100px; + overflow-y: hidden; + box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.75) inset; +} + +.map { + flex: 70%; + width: 100%; + height: 100%; + border-radius: 20px; + box-shadow: 0px 0px 25px 0px rgba(0,0,0,0.75); + margin: 50px; +} + +.flight-telemetry-container { + display: flex; + flex-direction: column; + flex: 15%; + height: 100%; + border-radius: 20px; + background-color: var(--light-bg); + padding: 20px; + padding-bottom: 0px; + box-shadow: 0px 0px 25px 0px rgba(0,0,0,0.75); +} + +.flight-telemetry { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + background-color: var(--main-bg-color); + border-radius: 10px; + margin-bottom: 20px; + box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.75) inset; + overflow: hidden; +} + +.data { + flex: 2; + font-family: 'Courier New'; + font-size: 45px; + font-size: 1.5dvw; + font-weight: bold; + margin: auto; +} + +.heading { + flex: 1; + font-size: 1.2dvw; + margin: auto; + margin-top: 1.5dvh; +} + +.unit-indicator { + display: flex; + align-self: center; + align-items: center; + width: auto; + height: 3dvh; + border-radius: 3dvh; + margin-bottom: 1dvw; +} + +.unit { + overflow: hidden; + display: flex; + flex: 1; + justify-content: center; + align-items: center; + font-size: 1dvw; + font-weight: bold; + padding: 0.5dvw; + height: 1dvh; + background-color: var(--highlight); + border-top-left-radius: 3dvh; + border-bottom-left-radius: 3dvh; + user-select: none +} + +.unit:last-child { + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + border-top-right-radius: 3dvh; + border-bottom-right-radius: 3dvh; + border-left: 2px solid black; +} + +#compass { + background-color: var(--highlight); +} + +@media (max-width: 1500px) { + .controls-page { + flex-direction: column; + padding: 25px; + padding-left: 50px; + padding-right: 50px; + } + + .flight-telemetry-container { + flex-direction: row; + padding-left: 0px; + margin: 0px; + width: 100%; + } + + .flight-telemetry { + margin-left: 20px; + height: auto; + } + + .map { + margin: 20px; + } + + .heading { + flex: 1; + font-size: 2dvw; + margin: auto; + margin-top: 1.5dvh; + } + + .data { + flex: 2; + font-family: 'Courier New'; + font-size: 45px; + font-size: 1.8dvw; + font-weight: bold; + margin: auto; + } +} \ No newline at end of file diff --git a/houston/src/pages/Control.tsx b/houston/src/pages/Control.tsx index 4209bab7..b11f8a09 100644 --- a/houston/src/pages/Control.tsx +++ b/houston/src/pages/Control.tsx @@ -1,9 +1,218 @@ +import React, { useState } from 'react'; +import TuasMap from '../components/TuasMap.tsx' +import "./Control.css" + +type Unit = 'knots' | 'm/s' | 'feet' | 'meters' | 'V' | '°F' | '°C' | ''; + +class Parameter { + values: number[]; + value: number; + units: Unit[]; + unit: Unit; + color: React.CSSProperties; + + // threshold: + // - length = 4 + // - format: [lower bound value for unit[0], upper bound value for unit[0], lower bound value for unit[1], upper bound value for unit[1]] + // - use: + // - to determine the color of the telemetry based on the current and threshold values. + // - if the current value is within the threshold, the color is green. if not, the color is red. + threshold: number[]; + + // index: + // - use: - + // - to determine which unit to display. + // - if the index is 0, display the value in unit[0]. if the index is 1, display the value in unit[1]. + // - value of index is toggled when the telemetry is clicked, which is handled by the handleClick function. + index: number; + + constructor(values: number[], units: Unit[], threshold: number[], index: number) { + this.values = values; + this.units = units; + this.value = values[index]; + this.unit = this.units[values.indexOf(this.value)]; + this.threshold = threshold; + this.color = colorDeterminer(this.values, this.value, this.threshold); + this.index = index; + } +} + +/** + * airspeed and groundspeed randomizer function for testing + * @returns the middle value of the array and its unit converted to the corresponding unit as a tuple + */ +function valueRandomizer() { + const number = [60, 120, 240]; + // const randomIndex = Math.floor(Math.random() * number.length); + return ([number[1], parseFloat((number[1]*1.94384).toFixed(2))]); +} + +/** + * altitude randomizer function for testing + * @returns the middle value of the array and its unit converted to the corresponding unit as a tuple + */ +function valueRandomizer2() { + const number = [60, 120, 240]; + // const randomIndex = Math.floor(Math.random() * number.length); + return ([number[1], parseFloat((number[1]*0.3048).toFixed(2))]); +} + +/** + * ESC temperature randomizer function for testing + * @returns the middle value of the array and its unit converted to the corresponding unit as a tuple + */ +function valueRandomizer3() { + const number = [60, 120, 240]; + // const randomIndex = Math.floor(Math.random() * number.length); + return ([number[1], parseFloat(((number[1]-32) * (5/9)).toFixed(2))]); +} + +/** + * battery randomizer function for testing + * @returns the middle value of the array twice to make implementation and logic easier + */ +function valueRandomizer4() { + const number = [60, 120, 240]; + // const randomIndex = Math.floor(Math.random() * number.length); + return ([number[1], number[1]]); +} + +/** + * color determiner function for testing + * @param values - the tuple of values + * @param value - the value of the telemetry + * @param threshold - the tuple of threshold values + * @returns - the color of the telemetry based on the current and threshold values + */ +function colorDeterminer(values : number[], value : number, threshold : number[]) { + if (value >= threshold[(values.indexOf(value)) * 2] && value <= threshold[(values.indexOf(value)) * 2 + 1]) { + return { backgroundColor: 'var(--success-text)' }; + } + else { + return { backgroundColor: 'var(--failure-text)' }; + } +} + +interface TelemetryProps { + key: number; + heading: string; + color: React.CSSProperties; + value: number; + units: Unit[]; + unit: Unit; + onClick: () => void; +} + /** - * Mission Control Page. TODO: write description. - * @returns Control Page + * Telemetry component + * @param props - the props of the telemetry + * @param props.key - the key of the telemetry + * @param props.heading - the heading of the telemetry + * @param props.color - the color of the telemetry + * @param props.value - the value of the telemetry + * @param props.units - the units of the telemetry + * @param props.unit - the current unit of the telemetry + * @param props.onClick - the onClick function of the telemetry + * @returns the telemetry component */ +function TelemetryGenerator({ key, heading, color, value, units, unit, onClick }: TelemetryProps) { + let unit0_color = { backgroundColor: '#808080' }; + let unit1_color = { backgroundColor: 'var(--secondary-text)' }; + if (units[0] !== units[1]) { + if (unit === units[0]) { + unit0_color = { backgroundColor: 'var(--highlight)' }; + unit1_color = { backgroundColor: '#808080' }; + } else { + unit0_color = { backgroundColor: '#808080' }; + unit1_color = { backgroundColor: 'var(--highlight)' }; + } + return ( +
+

{heading}

+

{value} {unit}

+
+

{units[0]}

+

{units[1]}

+
+
+ ); + } else { + return ( +
+

{heading}

+

{value} {unit}

+
+ ); + } +} + +/** + * control page + * @returns the control page + */ function Control() { - return

todo

+ const [index, setIndex] = useState([0, 0, 0, 0, 0, 0, 0]); + + const handleClick = (key : number) => { + setIndex(prevIndices => { + const newIndices = [...prevIndices]; + newIndices[key] = newIndices[key] === 0 ? 1 : 0; + return newIndices; + }); + }; + + const airspeedVal = valueRandomizer(); + const groundspeedVal = valueRandomizer(); + const altitudeMSLVal = valueRandomizer2(); + const altitudeAGLVal = valueRandomizer2(); + const motorBatteryVal = valueRandomizer4(); + const pixhawkBatteryVal = valueRandomizer4(); + const ESCtemperatureVal = valueRandomizer3(); + + const airspeedThreshold = [80, 160, parseFloat((80*1.94384).toFixed(2)), parseFloat((160*1.94384).toFixed(2))]; + const groundspeedThreshold = [80, 160, parseFloat((80*1.94384).toFixed(2)), parseFloat((160*1.94384).toFixed(2))]; + const altitudeMSLThreshold = [80, 160, parseFloat((80*0.3048).toFixed(2)), parseFloat((160*0.3048).toFixed(2))]; + const altitudeAGLThreshold = [80, 160, parseFloat((80*0.3048).toFixed(2)), parseFloat((160*0.3048).toFixed(2))]; + const motorBatteryThreshold = [80, 160, 80, 160]; + const pixhawkBatteryThreshold = [80, 160, 80, 160]; + const ESCtemperatureThreshold = [80, 160, parseFloat(((80-32) * (5/9)).toFixed(2)), parseFloat(((160-32) * (5/9)).toFixed(2))]; + + const airspeed = new Parameter(airspeedVal, ['knots', 'm/s'], airspeedThreshold, index[0]); + const groundspeed = new Parameter(groundspeedVal, ['knots', 'm/s'], groundspeedThreshold, index[1]); + const altitudeMSL = new Parameter(altitudeMSLVal, ['feet', 'meters'], altitudeMSLThreshold, index[2]); + const altitudeAGL = new Parameter(altitudeAGLVal, ['feet', 'meters'], altitudeAGLThreshold, index[3]); + const motorBattery = new Parameter(motorBatteryVal, ['V', 'V'], motorBatteryThreshold, index[4]); + const pixhawkBattery = new Parameter(pixhawkBatteryVal, ['V', 'V'], pixhawkBatteryThreshold, index[5]); + const ESCtemperature = new Parameter(ESCtemperatureVal, ['°F', '°C'], ESCtemperatureThreshold, index[6]); + + const flightMode = 'idk'; + const flightModeColor = { backgroundColor: 'var(--highlight)' }; + + return ( + <> +
+
+
+

*insert compass*

+
+ handleClick(0)}/> + handleClick(1)}/> + handleClick(2)}/> + handleClick(3)}/> +
+ +
+
+

Flight Mode

+

{flightMode}

+
+ handleClick(4)}/> + handleClick(5)}/> + handleClick(6)}/> +
+
+ + ); } export default Control; \ No newline at end of file