diff --git a/gcs/data/default_settings.json b/gcs/data/default_settings.json index f44ed0f54..db0c6ce60 100644 --- a/gcs/data/default_settings.json +++ b/gcs/data/default_settings.json @@ -9,7 +9,12 @@ "mapStyle": { "default": "hybrid", "type": "option", - "options": ["satellite", "hybrid", "streets", "outdoor"], + "options": [ + "satellite", + "hybrid", + "streets", + "outdoor" + ], "display": "Map Style" }, "autoCheckForUpdates": { @@ -21,6 +26,11 @@ "default": false, "type": "boolean", "display": "Sync Dashboard and Missions Map Viewstate" + }, + "speechAnnouncements": { + "default": false, + "type": "boolean", + "display": "Speech Announcements" } }, "Dashboard": { @@ -28,7 +38,10 @@ "description": "You'll be notified when the relative altitude of a drone exceeds this value.", "default": 120, "type": "number", - "range": [0, 100000], + "range": [ + 0, + 100000 + ], "display": "Maximum Altitude Alert", "suffix": "m", "group": "Altitude" @@ -37,16 +50,26 @@ "description": "You'll be notified when the relative altitude of a drone goes below these values.", "default": [], "type": "extendableNumber", - "range": [0, 100000], + "range": [ + 0, + 100000 + ], "display": "Minimum Altitude Alerts", "suffix": "m", "group": "Altitude" }, "batteryAlert": { "description": "You'll be notified when the battery percentage falls below these values.", - "default": [10, 20, 50], + "default": [ + 10, + 20, + 50 + ], "type": "extendableNumber", - "range": [0, 100], + "range": [ + 0, + 100 + ], "display": "Battery Alert", "suffix": "%", "group": "Battery" diff --git a/gcs/src/assets/sounds/flightmodechanged.mp3 b/gcs/src/assets/sounds/flightmodechanged.mp3 new file mode 100644 index 000000000..65c6a9ee1 Binary files /dev/null and b/gcs/src/assets/sounds/flightmodechanged.mp3 differ diff --git a/gcs/src/assets/sounds/lowbattery.mp3 b/gcs/src/assets/sounds/lowbattery.mp3 new file mode 100644 index 000000000..34e9904e1 Binary files /dev/null and b/gcs/src/assets/sounds/lowbattery.mp3 differ diff --git a/gcs/src/assets/sounds/waypointreached.mp3 b/gcs/src/assets/sounds/waypointreached.mp3 new file mode 100644 index 000000000..24f8c8301 Binary files /dev/null and b/gcs/src/assets/sounds/waypointreached.mp3 differ diff --git a/gcs/src/dashboard.jsx b/gcs/src/dashboard.jsx index 741901455..6bd72c6fc 100644 --- a/gcs/src/dashboard.jsx +++ b/gcs/src/dashboard.jsx @@ -42,6 +42,9 @@ import { selectHasSecondaryGps, } from "./redux/slices/droneInfoSlice" import { selectMessages } from "./redux/slices/statusTextSlice" +import { selectCurrentMission } from "./redux/slices/missionSlice" + +import { useSettings } from "./helpers/settings" // Helper javascript files import { GPS_FIX_TYPES } from "./helpers/mavlinkConstants" @@ -66,6 +69,9 @@ const tailwindColors = resolveConfig(tailwindConfig).theme.colors // Sounds import armSound from "./assets/sounds/armed.mp3" import disarmSound from "./assets/sounds/disarmed.mp3" +import lowBatterySound from "./assets/sounds/lowbattery.mp3" +import waypointReachedSound from "./assets/sounds/waypointreached.mp3" +import flightModeChangedSound from "./assets/sounds/flightmodechanged.mp3" export default function Dashboard() { const dispatch = useDispatch() @@ -83,6 +89,9 @@ export default function Dashboard() { const hdopDisplay = hdop != null ? hdop.toFixed(2) : "0.00" const connectedToDrone = useSelector(selectConnectedToDrone) + const currentMission = useSelector(selectCurrentMission) + + const { getSetting } = useSettings() const gps2 = useSelector(selectGPS2RawInt) const hasSecondaryGps = useSelector(selectHasSecondaryGps) @@ -122,14 +131,60 @@ export default function Dashboard() { // Sounds const [playArmed] = useSound(armSound, { volume: 0.1 }) const [playDisarmed] = useSound(disarmSound, { volume: 0.1 }) + const [playLowBattery] = useSound(lowBatterySound, { volume: 0.1 }) + const [playWaypointReached] = useSound(waypointReachedSound, { volume: 0.1 }) + const [playFlightModeChanged] = useSound(flightModeChangedSound, { + volume: 0.1, + }) + + const currentMissionSeqRef = useRef(currentMission.seq) // Play queued arming sounds useEffect(() => { - if (armedNotification !== "") { - armedNotification === "armed" ? playArmed() : playDisarmed() + if (!getSetting("General.speechAnnouncements")) { + return + } + switch (armedNotification) { + case "armed": + playArmed() + break + case "disarmed": + playDisarmed() + break + case "mode_change": + playFlightModeChanged() + break + case "low_battery": + playLowBattery() + break + default: + break + } + dispatch(soundPlayed()) + }, [ + armedNotification, + playArmed, + playDisarmed, + playLowBattery, + dispatch, + getSetting("General.speechAnnouncements"), + ]) + + useEffect(() => { + if (currentMission.seq > currentMissionSeqRef.current) { + if (!getSetting("General.speechAnnouncements")) { + return + } + playWaypointReached() dispatch(soundPlayed()) + currentMissionSeqRef.current = currentMission.seq } - }, [armedNotification, playArmed, playDisarmed, dispatch]) + }, [ + currentMission.seq, + playWaypointReached, + dispatch, + getSetting("General.speechAnnouncements"), + ]) // Following drone logic useEffect(() => { diff --git a/gcs/src/redux/slices/droneInfoSlice.js b/gcs/src/redux/slices/droneInfoSlice.js index e866cd567..c08852f1f 100644 --- a/gcs/src/redux/slices/droneInfoSlice.js +++ b/gcs/src/redux/slices/droneInfoSlice.js @@ -105,6 +105,7 @@ const droneInfoSlice = createSlice({ }, lastGraphResultsMessage: false, }, + lowBatteryAlerted: false, }, reducers: { setFlightSwVersion: (state, action) => { @@ -127,6 +128,10 @@ const droneInfoSlice = createSlice({ state.notificationSound = "disarmed" } state.heartbeatData.baseMode = action.payload.base_mode + + if (action.payload.custom_mode !== state.heartbeatData.customMode) { + state.notificationSound = "mode_change" + } state.heartbeatData.customMode = action.payload.custom_mode state.heartbeatData.systemStatus = action.payload.system_status }, @@ -139,6 +144,10 @@ const droneInfoSlice = createSlice({ } else { state.batteryData.push(action.payload) } + if (action.payload.battery_remaining <= 20 && !state.lowBatteryAlerted) { + state.notificationSound = "low_battery" + state.lowBatteryAlerted = true + } }, soundPlayed: (state) => { state.notificationSound = ""