Skip to content

Commit 1d6ef2b

Browse files
committed
[head_module]: setting menu for head limit
1 parent 2e83a4e commit 1d6ef2b

File tree

1 file changed

+164
-10
lines changed

1 file changed

+164
-10
lines changed

app/head_module/page.tsx

Lines changed: 164 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,74 @@ interface Marker {
2424

2525
export default function HeadModule() {
2626
const { isConnected, getRos, robotNamespace, ensureConnection } = useRos();
27-
const [roll, setRoll] = useState(0); // -80 to 80
28-
const [pitch, setPitch] = useState(0); // 0 to 90
27+
const [roll, setRoll] = useState(0);
28+
const [pitch, setPitch] = useState(0);
2929
const [override, setOverride] = useState(false);
30+
// Dynamic limits for roll and pitch
31+
const [rollMax, setRollMax] = useState<string>("80");
32+
const [rollMin, setRollMin] = useState<string>("-80");
33+
const [pitchMax, setPitchMax] = useState<string>("60");
34+
const [pitchMin, setPitchMin] = useState<string>("-60");
35+
36+
// Read limits from ROS2 params on mount/connection
37+
useEffect(() => {
38+
if (!isConnected) return;
39+
try {
40+
const ros = getRos();
41+
const paramClient = new ROSLIB.Service({
42+
ros: ros,
43+
name: "/head_controller/get_parameters",
44+
serviceType: "rcl_interfaces/srv/GetParameters",
45+
});
46+
const request = new ROSLIB.ServiceRequest({
47+
names: [
48+
"limit.roll_max",
49+
"limit.roll_min",
50+
"limit.pitch_max",
51+
"limit.pitch_min",
52+
],
53+
});
54+
paramClient.callService(request, (result) => {
55+
// result.values is an array of parameter values
56+
if (result && result.values && Array.isArray(result.values)) {
57+
result.values.forEach((param: any, idx: number) => {
58+
// param.double_value is the value
59+
if (typeof param.double_value === "number") {
60+
switch (idx) {
61+
case 0: setRollMax(String(param.double_value)); break;
62+
case 1: setRollMin(String(param.double_value)); break;
63+
case 2: setPitchMax(String(param.double_value)); break;
64+
case 3: setPitchMin(String(param.double_value)); break;
65+
}
66+
}
67+
});
68+
}
69+
}, () => {});
70+
} catch (e) {}
71+
// eslint-disable-next-line react-hooks/exhaustive-deps
72+
}, [isConnected]);
73+
74+
// Set parameter helper
75+
const setLimitParam = (paramName: string, value: number) => {
76+
if (!isConnected) return;
77+
try {
78+
const ros = getRos();
79+
const paramClient = new ROSLIB.Service({
80+
ros: ros,
81+
name: "/head_controller/set_parameters",
82+
serviceType: "rcl_interfaces/srv/SetParameters",
83+
});
84+
const parameter = new ROSLIB.Message({
85+
name: paramName,
86+
value: { type: 3, double_value: value },
87+
});
88+
const request = new ROSLIB.ServiceRequest({
89+
parameters: [parameter],
90+
});
91+
paramClient.callService(request, () => {}, () => {});
92+
} catch (e) {}
93+
};
94+
const [showSettings, setShowSettings] = useState(false);
3095

3196
// Ensure connection is maintained when component mounts
3297
useEffect(() => {
@@ -215,6 +280,95 @@ export default function HeadModule() {
215280
</header>
216281
<ConnectionStatusBar showFullControls={false} />
217282
<div className="max-w-xl mx-auto mt-12 bg-white border border-gray-200 shadow-sm rounded-lg p-8">
283+
{/* Settings Section */}
284+
<div className="flex justify-end mb-6">
285+
<button
286+
onClick={() => setShowSettings((prev) => !prev)}
287+
className="px-4 py-2 rounded-lg bg-gray-200 hover:bg-gray-300 text-gray-800 font-semibold border border-gray-300 shadow-sm transition-colors"
288+
>
289+
{showSettings ? 'Close Settings' : 'Open Settings'}
290+
</button>
291+
</div>
292+
{showSettings && (
293+
<div className="mb-10 p-4 border border-gray-300 rounded-lg bg-gray-50">
294+
<h3 className="text-lg font-semibold mb-4 text-gray-900">Head Limits Settings</h3>
295+
<div className="grid grid-cols-2 gap-4 mb-2">
296+
<div>
297+
<label className="block text-sm font-medium text-gray-700 mb-1">Roll Max (°)</label>
298+
<input
299+
type="text"
300+
inputMode="decimal"
301+
step="any"
302+
value={rollMax}
303+
onChange={e => {
304+
const val = e.target.value;
305+
setRollMax(val);
306+
const v = Number(val);
307+
if (!isNaN(v) && val !== "-") {
308+
setLimitParam("limit.roll_max", v);
309+
}
310+
}}
311+
className="w-full border rounded px-2 py-1"
312+
/>
313+
</div>
314+
<div>
315+
<label className="block text-sm font-medium text-gray-700 mb-1">Roll Min (°)</label>
316+
<input
317+
type="text"
318+
inputMode="decimal"
319+
step="any"
320+
value={rollMin}
321+
onChange={e => {
322+
const val = e.target.value;
323+
setRollMin(val);
324+
const v = Number(val);
325+
if (!isNaN(v) && val !== "-") {
326+
setLimitParam("limit.roll_min", v);
327+
}
328+
}}
329+
className="w-full border rounded px-2 py-1"
330+
/>
331+
</div>
332+
<div>
333+
<label className="block text-sm font-medium text-gray-700 mb-1">Pitch Max (°)</label>
334+
<input
335+
type="text"
336+
inputMode="decimal"
337+
step="any"
338+
value={pitchMax}
339+
onChange={e => {
340+
const val = e.target.value;
341+
setPitchMax(val);
342+
const v = Number(val);
343+
if (!isNaN(v) && val !== "-") {
344+
setLimitParam("limit.pitch_max", v);
345+
}
346+
}}
347+
className="w-full border rounded px-2 py-1"
348+
/>
349+
</div>
350+
<div>
351+
<label className="block text-sm font-medium text-gray-700 mb-1">Pitch Min (°)</label>
352+
<input
353+
type="text"
354+
inputMode="decimal"
355+
step="any"
356+
value={pitchMin}
357+
onChange={e => {
358+
const val = e.target.value;
359+
setPitchMin(val);
360+
const v = Number(val);
361+
if (!isNaN(v) && val !== "-") {
362+
setLimitParam("limit.pitch_min", v);
363+
}
364+
}}
365+
className="w-full border rounded px-2 py-1"
366+
/>
367+
</div>
368+
</div>
369+
<div className="text-xs text-gray-500 mt-2">These limits affect the range of the sliders below.</div>
370+
</div>
371+
)}
218372
{/* Search Mode Buttons */}
219373
<div className="mb-10 flex flex-col items-center">
220374
<label className="block text-lg font-semibold text-gray-900 mb-4">Search Mode</label>
@@ -249,32 +403,32 @@ export default function HeadModule() {
249403
<label className="block text-lg font-semibold text-gray-900 mb-4">Roll / Pan</label>
250404
<input
251405
type="range"
252-
min={-80}
253-
max={80}
406+
min={Number(rollMin)}
407+
max={Number(rollMax)}
254408
value={roll}
255409
onChange={e => handleRollChange(Number(e.target.value))}
256410
className="w-full accent-blue-600"
257411
/>
258412
<div className="flex justify-between text-sm text-gray-600 mt-2">
259-
<span>-80°</span>
413+
<span>{rollMin}°</span>
260414
<span>{roll}°</span>
261-
<span>80°</span>
415+
<span>{rollMax}°</span>
262416
</div>
263417
</div>
264418
<div className="mb-10">
265419
<label className="block text-lg font-semibold text-gray-900 mb-4">Pitch / Tilt</label>
266420
<input
267421
type="range"
268-
min={-60}
269-
max={60}
422+
min={Number(pitchMin)}
423+
max={Number(pitchMax)}
270424
value={pitch}
271425
onChange={e => handlePitchChange(Number(e.target.value))}
272426
className="w-full accent-green-600"
273427
/>
274428
<div className="flex justify-between text-sm text-gray-600 mt-2">
275-
<span>-60°</span>
429+
<span>{pitchMin}°</span>
276430
<span>{pitch}°</span>
277-
<span>60°</span>
431+
<span>{pitchMax}°</span>
278432
</div>
279433
</div>
280434
<div className="flex justify-center mt-8">

0 commit comments

Comments
 (0)