@@ -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+
464637app . whenReady ( ) . then ( async ( ) => {
465638 createWindow ( ) ;
466639
0 commit comments