Skip to content

Commit 0cb2e03

Browse files
committed
Fix keyboard control and serialization issue
1 parent dc3cd4a commit 0cb2e03

File tree

4 files changed

+335
-8
lines changed

4 files changed

+335
-8
lines changed

electron_demo/turtle_tf2/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,17 @@ world → turtle2
192192

193193
## Controls
194194

195+
### Keyboard Controls (NEW!)
196+
197+
**Turtle1 Movement**:
198+
199+
- **W** or **** (Up Arrow): Move forward
200+
- **S** or **** (Down Arrow): Move backward
201+
- **A** or **** (Left Arrow): Turn left
202+
- **D** or **** (Right Arrow): Turn right
203+
204+
💡 **Tip**: Click on the 3D visualization area first to ensure keyboard focus, then use the controls above to drive turtle1 around the turtlesim environment.
205+
195206
### Turtle Management
196207

197208
- **Spawn Turtle1**: Creates turtle1 at position (5.5, 5.5)

electron_demo/turtle_tf2/index.html

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,46 @@
214214
margin-right: 8px;
215215
}
216216

217+
.keyboard-legend {
218+
background: rgba(255, 255, 255, 0.8);
219+
border-radius: 8px;
220+
padding: 12px;
221+
margin: 8px 0;
222+
}
223+
224+
.key-combo {
225+
display: flex;
226+
align-items: center;
227+
margin: 6px 0;
228+
font-size: 13px;
229+
color: #555;
230+
}
231+
232+
.key {
233+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
234+
border: 1px solid #bbb;
235+
border-radius: 4px;
236+
padding: 4px 8px;
237+
margin: 0 4px;
238+
font-family: 'Courier New', monospace;
239+
font-weight: bold;
240+
font-size: 12px;
241+
color: #333;
242+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
243+
min-width: 24px;
244+
text-align: center;
245+
}
246+
247+
.keyboard-note {
248+
background: rgba(255, 193, 7, 0.1);
249+
border-left: 3px solid #ffc107;
250+
padding: 8px 12px;
251+
margin: 8px 0;
252+
font-size: 12px;
253+
color: #856404;
254+
border-radius: 0 4px 4px 0;
255+
}
256+
217257
.instructions {
218258
background: rgba(255, 255, 255, 0.3);
219259
border-radius: 8px;
@@ -341,6 +381,31 @@
341381
</div>
342382
</div>
343383

384+
<!-- Keyboard Controls -->
385+
<div class="controls-section">
386+
<div class="section-title">⌨️ Keyboard Controls</div>
387+
<div class="control-group">
388+
<label class="control-label">Move Turtle1</label>
389+
<div class="keyboard-legend">
390+
<div class="key-combo">
391+
<span class="key">W</span> or <span class="key"></span> - Forward
392+
</div>
393+
<div class="key-combo">
394+
<span class="key">S</span> or <span class="key"></span> - Backward
395+
</div>
396+
<div class="key-combo">
397+
<span class="key">A</span> or <span class="key"></span> - Turn Left
398+
</div>
399+
<div class="key-combo">
400+
<span class="key">D</span> or <span class="key"></span> - Turn Right
401+
</div>
402+
</div>
403+
<div class="keyboard-note">
404+
💡 Click on the 3D scene to focus, then use keyboard controls
405+
</div>
406+
</div>
407+
</div>
408+
344409
<!-- Transform Broadcasters -->
345410
<div class="controls-section">
346411
<div class="section-title">📡 TF2 Broadcasters</div>

electron_demo/turtle_tf2/main.js

Lines changed: 181 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ async function createTurtleTf2Broadcaster() {
129129
// Create transform broadcaster
130130
const tfBroadcaster = node.createPublisher('tf2_msgs/msg/TFMessage', '/tf');
131131

132+
// Create velocity publisher for turtle control
133+
const velocityPublisher = node.createPublisher(
134+
'geometry_msgs/msg/Twist',
135+
'/turtle1/cmd_vel'
136+
);
137+
turtleTf2Nodes.velocityPublisher = velocityPublisher;
138+
132139
// Subscribe to turtle1 pose
133140
node.createSubscription('turtlesim_msgs/msg/Pose', '/turtle1/pose', (msg) => {
134141
const now = node.now();
@@ -171,7 +178,29 @@ async function createTurtleTf2Broadcaster() {
171178
y: msg.y,
172179
theta: msg.theta,
173180
},
174-
transform: transform,
181+
transform: {
182+
header: {
183+
stamp: {
184+
sec: now.sec,
185+
nanosec: now.nanosec,
186+
},
187+
frame_id: 'world',
188+
},
189+
child_frame_id: 'turtle1',
190+
transform: {
191+
translation: {
192+
x: msg.x,
193+
y: msg.y,
194+
z: 0.0,
195+
},
196+
rotation: {
197+
x: 0.0,
198+
y: 0.0,
199+
z: Math.sin(msg.theta / 2.0),
200+
w: Math.cos(msg.theta / 2.0),
201+
},
202+
},
203+
},
175204
});
176205
}
177206
});
@@ -215,7 +244,29 @@ async function createTurtleTf2Broadcaster() {
215244
y: msg.y,
216245
theta: msg.theta,
217246
},
218-
transform: transform,
247+
transform: {
248+
header: {
249+
stamp: {
250+
sec: now.sec,
251+
nanosec: now.nanosec,
252+
},
253+
frame_id: 'world',
254+
},
255+
child_frame_id: 'turtle2',
256+
transform: {
257+
translation: {
258+
x: msg.x,
259+
y: msg.y,
260+
z: 0.0,
261+
},
262+
rotation: {
263+
x: 0.0,
264+
y: 0.0,
265+
z: Math.sin(msg.theta / 2.0),
266+
w: Math.cos(msg.theta / 2.0),
267+
},
268+
},
269+
},
219270
});
220271
}
221272
});
@@ -248,7 +299,34 @@ async function createTurtleTf2Listener() {
248299
// Process transforms for visualization
249300
msg.transforms.forEach((transform) => {
250301
if (mainWindow) {
251-
mainWindow.webContents.send('tf-transform-update', transform);
302+
// Create a serializable version of the transform
303+
const serializableTransform = {
304+
header: {
305+
stamp: {
306+
sec: transform.header.stamp.sec,
307+
nanosec: transform.header.stamp.nanosec,
308+
},
309+
frame_id: transform.header.frame_id,
310+
},
311+
child_frame_id: transform.child_frame_id,
312+
transform: {
313+
translation: {
314+
x: transform.transform.translation.x,
315+
y: transform.transform.translation.y,
316+
z: transform.transform.translation.z,
317+
},
318+
rotation: {
319+
x: transform.transform.rotation.x,
320+
y: transform.transform.rotation.y,
321+
z: transform.transform.rotation.z,
322+
w: transform.transform.rotation.w,
323+
},
324+
},
325+
};
326+
mainWindow.webContents.send(
327+
'tf-transform-update',
328+
serializableTransform
329+
);
252330
}
253331
});
254332
}
@@ -313,9 +391,10 @@ async function createStaticTurtleTf2Broadcaster() {
313391
);
314392

315393
// Broadcast a static transform (carrot frame relative to world)
394+
const now = node.now();
316395
const staticTransform = {
317396
header: {
318-
stamp: node.now(),
397+
stamp: now,
319398
frame_id: 'world',
320399
},
321400
child_frame_id: 'carrot1_static',
@@ -342,7 +421,34 @@ async function createStaticTurtleTf2Broadcaster() {
342421
staticTfBroadcaster.publish(staticTfMessage);
343422

344423
if (mainWindow) {
345-
mainWindow.webContents.send('static-transform-update', staticTransform);
424+
// Create serializable version
425+
const serializableStaticTransform = {
426+
header: {
427+
stamp: {
428+
sec: now.sec,
429+
nanosec: now.nanosec,
430+
},
431+
frame_id: 'world',
432+
},
433+
child_frame_id: 'carrot1_static',
434+
transform: {
435+
translation: {
436+
x: 2.0,
437+
y: 3.0,
438+
z: 0.0,
439+
},
440+
rotation: {
441+
x: 0.0,
442+
y: 0.0,
443+
z: 0.0,
444+
w: 1.0,
445+
},
446+
},
447+
};
448+
mainWindow.webContents.send(
449+
'static-transform-update',
450+
serializableStaticTransform
451+
);
346452
}
347453

348454
rclnodejs.spin(node);
@@ -389,7 +495,34 @@ async function createDynamicFrameTf2Broadcaster() {
389495
tfBroadcaster.publish(tfMessage);
390496

391497
if (mainWindow) {
392-
mainWindow.webContents.send('dynamic-transform-update', dynamicTransform);
498+
// Create serializable version
499+
const serializableDynamicTransform = {
500+
header: {
501+
stamp: {
502+
sec: now.sec,
503+
nanosec: now.nanosec,
504+
},
505+
frame_id: 'turtle1',
506+
},
507+
child_frame_id: 'carrot1_dynamic',
508+
transform: {
509+
translation: {
510+
x: 2.0 * Math.sin(x),
511+
y: 2.0 * Math.cos(x),
512+
z: 0.0,
513+
},
514+
rotation: {
515+
x: 0.0,
516+
y: 0.0,
517+
z: 0.0,
518+
w: 1.0,
519+
},
520+
},
521+
};
522+
mainWindow.webContents.send(
523+
'dynamic-transform-update',
524+
serializableDynamicTransform
525+
);
393526
}
394527
});
395528

@@ -405,9 +538,10 @@ async function createFixedFrameTf2Broadcaster() {
405538

406539
// Timer to broadcast fixed transform
407540
const timer = node.createTimer(100, () => {
541+
const now = node.now();
408542
const fixedTransform = {
409543
header: {
410-
stamp: node.now(),
544+
stamp: now,
411545
frame_id: 'turtle1',
412546
},
413547
child_frame_id: 'carrot1_fixed',
@@ -433,7 +567,34 @@ async function createFixedFrameTf2Broadcaster() {
433567
tfBroadcaster.publish(tfMessage);
434568

435569
if (mainWindow) {
436-
mainWindow.webContents.send('fixed-transform-update', fixedTransform);
570+
// Create serializable version
571+
const serializableFixedTransform = {
572+
header: {
573+
stamp: {
574+
sec: now.sec,
575+
nanosec: now.nanosec,
576+
},
577+
frame_id: 'turtle1',
578+
},
579+
child_frame_id: 'carrot1_fixed',
580+
transform: {
581+
translation: {
582+
x: 0.0,
583+
y: 2.0,
584+
z: 0.0,
585+
},
586+
rotation: {
587+
x: 0.0,
588+
y: 0.0,
589+
z: 0.0,
590+
w: 1.0,
591+
},
592+
},
593+
};
594+
mainWindow.webContents.send(
595+
'fixed-transform-update',
596+
serializableFixedTransform
597+
);
437598
}
438599
});
439600

@@ -461,6 +622,18 @@ ipcMain.on('spawn-turtle-request', async (event, data) => {
461622
}
462623
});
463624

625+
// Handle keyboard turtle control commands
626+
ipcMain.on('turtle-cmd-vel', (event, data) => {
627+
if (turtleTf2Nodes.velocityPublisher) {
628+
const velocity = {
629+
linear: data.linear,
630+
angular: data.angular,
631+
};
632+
// Send velocity command to turtle1
633+
turtleTf2Nodes.velocityPublisher.publish(velocity);
634+
}
635+
});
636+
464637
app.whenReady().then(async () => {
465638
createWindow();
466639

0 commit comments

Comments
 (0)