Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
aedc760
Added enable gripper button
ProgramPhantom Feb 19, 2026
f3eec7d
Created separate gripper enable button and removed the deactivation o…
ProgramPhantom Feb 19, 2026
1c69c89
Added emitRefreshParams to toggleGripperEnabled
ProgramPhantom Feb 19, 2026
c6fa762
- Added endpoints for enabling and disabling gripper
ProgramPhantom Feb 22, 2026
59bd004
Merge branch 'main' into 897-enabledisable-gripper-from-gripper-page
ProgramPhantom Feb 22, 2026
092ce99
Update gcs/src/components/config/gripper.jsx
ProgramPhantom Feb 22, 2026
5cd049c
Update gcs/src/components/config/gripper.jsx
ProgramPhantom Feb 22, 2026
81115d6
Update gcs/src/components/config/gripper.jsx
ProgramPhantom Feb 22, 2026
37ea3dc
- Changed socket emit names to end in _result
ProgramPhantom Feb 22, 2026
82c0fb2
Merge branch '897-enabledisable-gripper-from-gripper-page' of https:/…
ProgramPhantom Feb 22, 2026
066107c
Merge branch 'main' into 897-enabledisable-gripper-from-gripper-page
ProgramPhantom Feb 22, 2026
ec3731b
- Added tests and formatted. First time doing these tests
ProgramPhantom Feb 22, 2026
cd04360
Merge branch '897-enabledisable-gripper-from-gripper-page' of https:/…
ProgramPhantom Feb 22, 2026
d78dd36
- Moved gripper config refresh to be triggered automatically by middl…
ProgramPhantom Feb 23, 2026
abf9766
Merge branch 'main' into 897-enabledisable-gripper-from-gripper-page
1Blademaster Feb 25, 2026
fe8702e
Fixed ruff formatting
ProgramPhantom Feb 25, 2026
4974b57
Try to fix python tests
ProgramPhantom Feb 25, 2026
6d0e1f9
Fix tests again
ProgramPhantom Feb 25, 2026
8b1b5b3
Added success notifications
ProgramPhantom Feb 25, 2026
a6a1b01
Fixed fetch error of gripper params
ProgramPhantom Feb 25, 2026
1bdf003
Alternative fix
ProgramPhantom Feb 26, 2026
ce3da6e
- Removed random getGripperEnabled
ProgramPhantom Feb 26, 2026
e6df21c
Turned off initial fetch of gripper config so it only fires if grippe…
ProgramPhantom Feb 27, 2026
78aafb1
Merge branch '897-enabledisable-gripper-from-gripper-page' of https:/…
ProgramPhantom Feb 27, 2026
ab33dc9
Merge branch 'main' into 897-enabledisable-gripper-from-gripper-page
ProgramPhantom Feb 27, 2026
43c55c4
Added getGripperEnabled to dependency list
ProgramPhantom Feb 27, 2026
392d162
Merge branch '897-enabledisable-gripper-from-gripper-page' of https:/…
ProgramPhantom Feb 27, 2026
af6e566
- Removed get gripper config from set gripper disabled
ProgramPhantom Feb 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions gcs/src/components/config/gripper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ import { IconInfoCircle } from "@tabler/icons-react"
import { useDispatch, useSelector } from "react-redux"
import {
emitGetGripperConfig,
emitGetGripperEnabled,
emitSetGripper,
emitSetGripperConfigParam,
emitSetGripperDisabled,
emitSetGripperEnabled,
selectGetGripperEnabled,
selectGripperConfig,
selectRefreshingGripperConfigData,
Expand Down Expand Up @@ -126,13 +129,25 @@ export default function Gripper() {
}

dispatch(emitSetState("config.gripper"))
dispatch(emitGetGripperConfig())
}, [connected, dispatch])

dispatch(emitGetGripperEnabled())
if (getGripperEnabled) {
dispatch(emitGetGripperConfig())
}
}, [connected, dispatch, getGripperEnabled])

function setGripper(action) {
dispatch(emitSetGripper(action))
}

const toggleGripperEnabled = () => {
if (getGripperEnabled) {
dispatch(emitSetGripperDisabled())
} else {
dispatch(emitSetGripperEnabled())
}
}

const debouncedUpdate = useDebouncedCallback((param_id, value) => {
dispatch(emitSetGripperConfigParam({ param_id, value }))
}, 500)
Expand All @@ -141,6 +156,10 @@ export default function Gripper() {
return (
<div className="flex flex-col gap-4 mx-4">
<p>Gripper is not enabled.</p>

<Button w={"30%"} onClick={toggleGripperEnabled} color={"green"}>
Enable Gripper
</Button>
</div>
)
}
Expand All @@ -152,14 +171,20 @@ export default function Gripper() {
zIndex={1000}
overlayProps={{ blur: 2 }}
/>
<div className="flex flex-row gap-2">
<Button onClick={() => setGripper("release")} color={"red"}>
Release Gripper
</Button>
<Button onClick={() => setGripper("grab")} color={"red"}>
Grab Gripper
<div className="flex flex-col gap-2 w-1/3">
<Button onClick={() => toggleGripperEnabled()} color={"red"}>
Disable Gripper
</Button>
<div className="flex flex-row gap-2">
<Button w="100%" onClick={() => setGripper("release")} color={"blue"}>
Release Gripper
</Button>
<Button w="100%" onClick={() => setGripper("grab")} color={"blue"}>
Grab Gripper
</Button>
</div>
</div>

<div className="flex flex-col gap-2">
{params.map((param) => (
<div key={param.param_id} className="flex flex-row justify-between">
Expand Down
14 changes: 2 additions & 12 deletions gcs/src/config.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,18 @@ import NoDroneConnected from "./components/noDroneConnected"
// Redux
import { useDispatch, useSelector } from "react-redux"
import Ftp from "./components/config/ftp"
import {
emitGetGripperEnabled,
selectActiveTab,
selectGetGripperEnabled,
setActiveTab,
} from "./redux/slices/configSlice"
import { selectActiveTab, setActiveTab } from "./redux/slices/configSlice"
import { selectConnectedToDrone } from "./redux/slices/droneConnectionSlice"

export default function Config() {
const dispatch = useDispatch()
const connected = useSelector(selectConnectedToDrone)
const getGripperEnabled = useSelector(selectGetGripperEnabled)
const activeTab = useSelector(selectActiveTab)
const paddingTop = "mt-4"

useEffect(() => {
if (!connected) {
dispatch(setActiveTab(null))
} else {
dispatch(emitGetGripperEnabled())
}
}, [connected])

Expand All @@ -57,9 +49,7 @@ export default function Config() {
onChange={(newTab) => dispatch(setActiveTab(newTab))}
>
<Tabs.List>
<Tabs.Tab value="gripper" disabled={!getGripperEnabled}>
Gripper
</Tabs.Tab>
<Tabs.Tab value="gripper">Gripper</Tabs.Tab>
<Tabs.Tab value="motor_test">Motor Test</Tabs.Tab>
<Tabs.Tab value="rc_calibration">RC Calibration</Tabs.Tab>
<Tabs.Tab value="flightmodes">Flight modes</Tabs.Tab>
Expand Down
10 changes: 10 additions & 0 deletions gcs/src/redux/middleware/emitters.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
emitSetFlightModeChannel,
emitSetGripper,
emitSetGripperConfigParam,
emitSetGripperDisabled,
emitSetGripperEnabled,
emitSetRcConfigParam,
emitTestAllMotors,
emitTestMotorSequence,
Expand Down Expand Up @@ -333,6 +335,14 @@ export function handleEmitters(socket, store, action) {
emitter: emitGetGripperEnabled,
callback: () => socket.socket.emit("get_gripper_enabled"),
},
{
emitter: emitSetGripperEnabled,
callback: () => socket.socket.emit("set_gripper_enabled"),
},
{
emitter: emitSetGripperDisabled,
callback: () => socket.socket.emit("set_gripper_disabled"),
},
{
emitter: emitGetGripperConfig,
callback: () => {
Expand Down
28 changes: 28 additions & 0 deletions gcs/src/redux/middleware/socketMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
import SocketFactory from "../../helpers/socket"
import {
emitGetFlightModeConfig,
emitGetGripperConfig,
setChannelsConfig,
setCurrentPwmValue,
setFlightModeChannel,
Expand Down Expand Up @@ -187,6 +188,8 @@ const MissionSpecificSocketEvents = Object.freeze({

const ConfigSpecificSocketEvents = Object.freeze({
onGripperEnabled: "is_gripper_enabled",
onSetGripperEnabledResult: "set_gripper_enabled_result",
onSetGripperDisabledResult: "set_gripper_disabled_result",
onSetGripperResult: "set_gripper_result",
onGripperConfig: "gripper_config",
setGripperParamResult: "set_gripper_param_result",
Expand Down Expand Up @@ -1081,6 +1084,31 @@ const socketMiddleware = (store) => {
},
)

socket.socket.on(
ConfigSpecificSocketEvents.onSetGripperEnabledResult,
(result) => {
if (result.success) {
store.dispatch(setGetGripperEnabled(true))
store.dispatch(emitGetGripperConfig())
showSuccessNotification(result.message)
} else {
showErrorNotification(result.message)
}
},
)

socket.socket.on(
ConfigSpecificSocketEvents.onSetGripperDisabledResult,
(result) => {
if (result.success) {
store.dispatch(setGetGripperEnabled(false))
showSuccessNotification(result.message)
} else {
showErrorNotification(result.message)
}
},
)

socket.socket.on(
ConfigSpecificSocketEvents.onSetGripperResult,
(msg) => {
Expand Down
4 changes: 4 additions & 0 deletions gcs/src/redux/slices/configSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ const configSlice = createSlice({

// Emits
emitGetGripperEnabled: () => {},
emitSetGripperEnabled: () => {},
emitSetGripperDisabled: () => {},
emitGetGripperConfig: () => {},
emitSetGripperConfigParam: () => {},
emitGetFlightModeConfig: () => {},
Expand Down Expand Up @@ -207,6 +209,8 @@ export const {
setRadioCalibrationModalOpen,

emitGetGripperEnabled,
emitSetGripperEnabled,
emitSetGripperDisabled,
emitGetGripperConfig,
emitSetGripperConfigParam,
emitGetFlightModeConfig,
Expand Down
34 changes: 34 additions & 0 deletions radio/app/controllers/gripperController.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,40 @@ def setGripper(self, action: str) -> Response:
finally:
self.drone.release_message_type("COMMAND_ACK", self.controller_id)

def enableGripper(self) -> Response:
"""
Enable the gripper
"""

success = self.drone.paramsController.setParam(
"GRIP_ENABLE", 1, mavutil.mavlink.MAV_PARAM_TYPE_UINT8
)

if success:
return {"success": True, "message": "Enabled gripper"}
else:
return {
"success": False,
"message": "Failed to enable gripper",
}

def disableGripper(self) -> Response:
"""
Disable the gripper
"""

success = self.drone.paramsController.setParam(
"GRIP_ENABLE", 0, mavutil.mavlink.MAV_PARAM_TYPE_UINT8
)

if success:
return {"success": True, "message": "Disabled gripper"}
else:
return {
"success": False,
"message": "Failed to disable gripper",
}

def getConfig(self) -> dict:
"""
Get the current gripper config from cached parameters.
Expand Down
46 changes: 46 additions & 0 deletions radio/app/endpoints/gripper.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,46 @@ def getGripperEnabled() -> None:
socketio.emit("is_gripper_enabled", enabled)


@socketio.on("set_gripper_enabled")
def setGripperEnabled() -> None:
"""
Enables the gripper
"""

if not droneStatus.drone:
droneErrorCb("You must be connected to the drone to access the gripper.")
logger.warning("Attempted to set gripper enabled when drone is None.")
return

if droneStatus.state != "config.gripper":
droneErrorCb("You must be on the gripper page to enable the gripper")
logger.warning("Attempted to set gripper enabled not on gripper page")
return

result = droneStatus.drone.gripperController.enableGripper()
socketio.emit("set_gripper_enabled_result", result)


@socketio.on("set_gripper_disabled")
def setGripperDisabled() -> None:
"""
Disable the gripper
"""

if not droneStatus.drone:
droneErrorCb("You must be connected to the drone to access the gripper.")
logger.warning("Attempted to set gripper disabled when drone is None.")
return

if droneStatus.state != "config.gripper":
droneErrorCb("You must be on the gripper page to disable the gripper")
logger.warning("Attempted to set gripper disabled not on gripper page")
return

result = droneStatus.drone.gripperController.disableGripper()
socketio.emit("set_gripper_disabled_result", result)


Comment thread
ProgramPhantom marked this conversation as resolved.
@socketio.on("set_gripper")
def setGripper(action: str) -> None:
"""
Expand Down Expand Up @@ -67,6 +107,12 @@ def getGripperConfig() -> None:
droneErrorCb("get the gripper config")
return

# Refresh gripper params from drone, if there's no cache
if droneStatus.drone.gripperController.getEnabled() and (
not bool(droneStatus.drone.gripperController.params)
):
droneStatus.drone.gripperController.getGripperParams()

gripper_config = droneStatus.drone.gripperController.getConfig()

socketio.emit(
Expand Down
2 changes: 1 addition & 1 deletion radio/app/endpoints/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ def log_worker(result: dict):


def validate_ports(
ports: Optional[list[dict[str, Any]]]
ports: Optional[list[dict[str, Any]]],
) -> Tuple[dict[str, str], Optional[int]]:
"""
Construct the validated port mappings and primary host port
Expand Down
16 changes: 16 additions & 0 deletions radio/tests/test_gripper.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,19 @@ def test_gripperDisabled(socketio_client: SocketIOTestClient, droneStatus) -> No
"success": False,
"message": "Gripper is not enabled",
}


@falcon_test()
def test_setGripperEnabled(socketio_client: SocketIOTestClient) -> None:
assert send_and_receive("set_gripper_enabled") == {
"success": True,
"message": "Enabled gripper",
}


@falcon_test()
def test_setGripperDisabled(socketio_client: SocketIOTestClient) -> None:
assert send_and_receive("set_gripper_disabled") == {
"success": True,
"message": "Disabled gripper",
}
Loading