@@ -50,6 +50,12 @@ public class EphysLinkService
5050
5151 #endregion
5252
53+ #region Demo Loop
54+
55+ private readonly Dictionary < string , bool > _runningDemoLoops = new ( ) ;
56+ private const float DEMO_SPEED = 100f ; // µm/s
57+ #endregion
58+
5359 public EphysLinkService ( StoreService storeService )
5460 {
5561 // Register services.
@@ -69,6 +75,27 @@ private async void OnSceneStateChanged(SceneState sceneState)
6975 // Apply small delay to prevent overrunning updates (delay for roughly 60 FPS).
7076 await Task . Delay ( 10 ) ;
7177
78+ // Handle demo loop state changes.
79+ foreach ( var manipulatorState in sceneState . Manipulators )
80+ {
81+ var isRunning =
82+ _runningDemoLoops . ContainsKey ( manipulatorState . Id )
83+ && _runningDemoLoops [ manipulatorState . Id ] ;
84+
85+ switch ( manipulatorState . IsDemoRunning )
86+ {
87+ // Start demo loop if requested and not already running.
88+ case true when ! isRunning :
89+ _runningDemoLoops [ manipulatorState . Id ] = true ;
90+ _ = RunDemoLoop ( manipulatorState . Id ) ;
91+ break ;
92+ // Stop demo loop if requested and currently running.
93+ case false when isRunning :
94+ await Stop ( manipulatorState . Id ) ;
95+ break ;
96+ }
97+ }
98+
7299 // WARNING: this will create an infinite loop of state updates on purpose.
73100 // Update the position of visualization probes.
74101 await UpdateVisualizationProbePosition ( sceneState ) ;
@@ -738,6 +765,155 @@ public async Task SetManipulatorDemoTargetCoordinateToCurrentPosition(string man
738765 ) ;
739766 }
740767
768+ /// <summary>
769+ /// Run the demo loop for a manipulator.
770+ /// Sequence: Home -> Target (X,Z only) -> Target (Y,W only) -> Back to Target (X,Z only) -> repeat indefinitely
771+ /// until IsDemoRunning is set to false or an error occurs.
772+ /// </summary>
773+ /// <param name="manipulatorId">ID of the manipulator to run demo loop for.</param>
774+ private async Task RunDemoLoop ( string manipulatorId )
775+ {
776+ while ( true )
777+ {
778+ // Get current manipulator state.
779+ var sceneState = _storeService . Store . GetState < SceneState > ( SliceNames . SCENE_SLICE ) ;
780+ var manipulatorState = sceneState . Manipulators . FirstOrDefault ( m =>
781+ m . Id == manipulatorId
782+ ) ;
783+
784+ // Exit if manipulator not found.
785+ if ( manipulatorState == null )
786+ {
787+ _runningDemoLoops [ manipulatorId ] = false ;
788+ return ;
789+ }
790+
791+ // Exit if demo is no longer running.
792+ if ( ! manipulatorState . IsDemoRunning )
793+ {
794+ _runningDemoLoops [ manipulatorId ] = false ;
795+ return ;
796+ }
797+
798+ // Step 1: Move to home position.
799+ var homeRequest = new SetPositionRequest (
800+ manipulatorId ,
801+ manipulatorState . DemoHomeCoordinate ,
802+ DEMO_SPEED
803+ ) ;
804+ var homeResponse = await SetPosition ( homeRequest ) ;
805+
806+ // Exit on error (including stop request).
807+ if ( HasError ( homeResponse . Error ) )
808+ {
809+ _runningDemoLoops [ manipulatorId ] = false ;
810+ return ;
811+ }
812+
813+ // Refresh state and check if demo is still running.
814+ sceneState = _storeService . Store . GetState < SceneState > ( SliceNames . SCENE_SLICE ) ;
815+ manipulatorState = sceneState . Manipulators . FirstOrDefault ( m =>
816+ m . Id == manipulatorId
817+ ) ;
818+ if ( manipulatorState is not { IsDemoRunning : true } )
819+ {
820+ _runningDemoLoops [ manipulatorId ] = false ;
821+ return ;
822+ }
823+
824+ // Step 2: Move to target position on X and Z axes only (keep Y and W from home).
825+ var intermediatePosition = new Vector4 (
826+ manipulatorState . DemoTargetCoordinate . x ,
827+ manipulatorState . DemoHomeCoordinate . y ,
828+ manipulatorState . DemoTargetCoordinate . z ,
829+ manipulatorState . DemoHomeCoordinate . w
830+ ) ;
831+ var intermediateRequest = new SetPositionRequest (
832+ manipulatorId ,
833+ intermediatePosition ,
834+ DEMO_SPEED
835+ ) ;
836+ var intermediateResponse = await SetPosition ( intermediateRequest ) ;
837+
838+ // Exit on error (including stop request).
839+ if ( HasError ( intermediateResponse . Error ) )
840+ {
841+ _runningDemoLoops [ manipulatorId ] = false ;
842+ return ;
843+ }
844+
845+ // Refresh state and check if demo is still running.
846+ sceneState = _storeService . Store . GetState < SceneState > ( SliceNames . SCENE_SLICE ) ;
847+ manipulatorState = sceneState . Manipulators . FirstOrDefault ( m =>
848+ m . Id == manipulatorId
849+ ) ;
850+ if ( manipulatorState is not { IsDemoRunning : true } )
851+ {
852+ _runningDemoLoops [ manipulatorId ] = false ;
853+ return ;
854+ }
855+
856+ // Step 3: Move to target position on Y and W axes only (complete movement to target).
857+ var targetRequest = new SetPositionRequest (
858+ manipulatorId ,
859+ manipulatorState . DemoTargetCoordinate ,
860+ DEMO_SPEED
861+ ) ;
862+ var targetResponse = await SetPosition ( targetRequest ) ;
863+
864+ // Exit on error (including stop request).
865+ if ( HasError ( targetResponse . Error ) )
866+ {
867+ _runningDemoLoops [ manipulatorId ] = false ;
868+ return ;
869+ }
870+
871+ // Refresh state and check if demo is still running.
872+ sceneState = _storeService . Store . GetState < SceneState > ( SliceNames . SCENE_SLICE ) ;
873+ manipulatorState = sceneState . Manipulators . FirstOrDefault ( m =>
874+ m . Id == manipulatorId
875+ ) ;
876+ if ( manipulatorState is not { IsDemoRunning : true } )
877+ {
878+ _runningDemoLoops [ manipulatorId ] = false ;
879+ return ;
880+ }
881+
882+ // Step 4: Move back to intermediate position (X,Z only, keeping Y,W from home).
883+ var returnToIntermediatePosition = new Vector4 (
884+ manipulatorState . DemoTargetCoordinate . x ,
885+ manipulatorState . DemoHomeCoordinate . y ,
886+ manipulatorState . DemoTargetCoordinate . z ,
887+ manipulatorState . DemoHomeCoordinate . w
888+ ) ;
889+ var returnToIntermediateRequest = new SetPositionRequest (
890+ manipulatorId ,
891+ returnToIntermediatePosition ,
892+ DEMO_SPEED
893+ ) ;
894+ var returnToIntermediateResponse = await SetPosition ( returnToIntermediateRequest ) ;
895+
896+ // Exit on error (including stop request).
897+ if ( HasError ( returnToIntermediateResponse . Error ) )
898+ {
899+ _runningDemoLoops [ manipulatorId ] = false ;
900+ return ;
901+ }
902+
903+ // Refresh state and check if demo is still running before next iteration.
904+ sceneState = _storeService . Store . GetState < SceneState > ( SliceNames . SCENE_SLICE ) ;
905+ manipulatorState = sceneState . Manipulators . FirstOrDefault ( m =>
906+ m . Id == manipulatorId
907+ ) ;
908+ if ( manipulatorState is { IsDemoRunning : true } )
909+ continue ;
910+ _runningDemoLoops [ manipulatorId ] = false ;
911+ return ;
912+
913+ // Loop continues to next iteration (back to home).
914+ }
915+ }
916+
741917 #endregion
742918 }
743919}
0 commit comments