@@ -74,8 +74,8 @@ struct SequenceState {
7474 bool waiting_for_user = false ;
7575 bool waiting_for_tcsop = false ;
7676 bool user_wait_after_failure = false ;
77- bool continue_will_expose = false ;
78- bool continue_starts_acquisition = false ;
77+ bool user_gate_action_polled = false ;
78+ std::string user_gate_action = " NONE " ; // NONE|ACQUIRE|EXPOSE|OFFSET_EXPOSE
7979 bool ontarget = false ;
8080 bool guiding_on = false ;
8181 bool guiding_failed = false ;
@@ -106,8 +106,8 @@ struct SequenceState {
106106 waiting_for_user = false ;
107107 waiting_for_tcsop = false ;
108108 user_wait_after_failure = false ;
109- continue_will_expose = false ;
110- continue_starts_acquisition = false ;
109+ user_gate_action_polled = false ;
110+ user_gate_action = " NONE " ;
111111 ontarget = false ;
112112 guiding_on = false ;
113113 guiding_failed = false ;
@@ -135,8 +135,8 @@ struct SequenceState {
135135 waiting_for_user = false ;
136136 waiting_for_tcsop = false ;
137137 user_wait_after_failure = false ;
138- continue_will_expose = false ;
139- continue_starts_acquisition = false ;
138+ user_gate_action_polled = false ;
139+ user_gate_action = " NONE " ;
140140 ontarget = false ;
141141 guiding_on = false ;
142142 guiding_failed = false ;
@@ -717,18 +717,26 @@ class SeqProgressGui {
717717 }
718718
719719 void draw_user_instruction () {
720- if (!state_.waiting_for_user || !state_. continue_will_expose ) return ;
720+ if (!state_.waiting_for_user ) return ;
721721 if (!blink_on_) return ; // blink the instruction for visibility
722722
723- bool has_offset = (state_.offset_ra != 0.0 || state_.offset_dec != 0.0 );
724723 char instruction[256 ];
725- if (has_offset) {
724+ if (!state_.user_gate_action_polled || state_.user_gate_action == " NONE" ) {
725+ snprintf (instruction, sizeof (instruction),
726+ " >>> Click CONTINUE <<<" );
727+ } else if (state_.user_gate_action == " ACQUIRE" ) {
728+ snprintf (instruction, sizeof (instruction),
729+ " >>> Click ACQUIRE to start acquisition <<<" );
730+ } else if (state_.user_gate_action == " OFFSET_EXPOSE" ) {
726731 snprintf (instruction, sizeof (instruction),
727732 " >>> Click OFFSET & EXPOSE to apply offset (RA=%.2f\" DEC=%.2f\" ) then expose <<<" ,
728733 state_.offset_ra , state_.offset_dec );
729- } else {
734+ } else if (state_. user_gate_action == " EXPOSE " ) {
730735 snprintf (instruction, sizeof (instruction),
731736 " >>> Click EXPOSE to begin exposure (no target offset) <<<" );
737+ } else {
738+ snprintf (instruction, sizeof (instruction),
739+ " >>> Waiting for sequencer USER action details... <<<" );
732740 }
733741
734742 XSetForeground (display_, gc_, color_wait_); // red bold
@@ -737,13 +745,14 @@ class SeqProgressGui {
737745
738746 void draw_buttons () {
739747 draw_button (ontarget_btn_, " ONTARGET" , state_.waiting_for_tcsop );
740- const char *continue_label = " EXPOSE" ;
741- if (state_.waiting_for_user ) {
742- if (state_.continue_starts_acquisition ) {
743- continue_label = " START ACQUISITION" ;
744- } else {
745- bool has_offset = (state_.offset_ra != 0.0 || state_.offset_dec != 0.0 );
746- continue_label = has_offset ? " OFFSET & EXPOSE" : " EXPOSE" ;
748+ const char *continue_label = " CONTINUE" ;
749+ if (state_.waiting_for_user && state_.user_gate_action_polled ) {
750+ if (state_.user_gate_action == " ACQUIRE" ) {
751+ continue_label = " ACQUIRE" ;
752+ } else if (state_.user_gate_action == " OFFSET_EXPOSE" ) {
753+ continue_label = " OFFSET & EXPOSE" ;
754+ } else if (state_.user_gate_action == " EXPOSE" ) {
755+ continue_label = " EXPOSE" ;
747756 }
748757 }
749758 draw_button (continue_btn_, continue_label, state_.waiting_for_user );
@@ -947,6 +956,21 @@ class SeqProgressGui {
947956 if (jmessage.contains (" acqmode" ) && jmessage[" acqmode" ].is_number ()) {
948957 state_.acqmode = jmessage[" acqmode" ].get <int >();
949958 }
959+ if (jmessage.contains (" user_gate_action" ) && jmessage[" user_gate_action" ].is_string ()) {
960+ std::string gate = to_upper_copy (jmessage[" user_gate_action" ].get <std::string>());
961+ if (gate != " ACQUIRE" && gate != " EXPOSE" && gate != " OFFSET_EXPOSE" && gate != " NONE" ) {
962+ gate = " NONE" ;
963+ }
964+ if (state_.waiting_for_user ) {
965+ if (gate != " NONE" ) {
966+ state_.user_gate_action = gate;
967+ state_.user_gate_action_polled = true ;
968+ }
969+ } else {
970+ state_.user_gate_action = gate;
971+ state_.user_gate_action_polled = false ;
972+ }
973+ }
950974 if (jmessage.contains (" nexp" ) && jmessage[" nexp" ].is_number ()) {
951975 int new_nexp = jmessage[" nexp" ].get <int >();
952976 if (new_nexp != state_.nexp ) {
@@ -1030,6 +1054,12 @@ class SeqProgressGui {
10301054 const bool allow_tcp_poll = !have_zmq || (zmq_quiet && stale_seq);
10311055
10321056 if (options_.poll_ms > 0 ) {
1057+ // During USER wait, keep polling snapshots until gate action arrives.
1058+ if (have_zmq && zmq_pub_ && state_.waiting_for_user && !state_.user_gate_action_polled &&
1059+ std::chrono::duration_cast<std::chrono::milliseconds>(now - last_snapshot_request_).count () >= 1000 ) {
1060+ request_snapshot ();
1061+ updated = true ;
1062+ }
10331063 if (have_zmq && zmq_pub_) {
10341064 if (stale_seq &&
10351065 std::chrono::duration_cast<std::chrono::milliseconds>(now - last_snapshot_request_).count () >= stale_ms) {
@@ -1137,6 +1167,7 @@ class SeqProgressGui {
11371167 }
11381168
11391169 void handle_waitstate (const std::string &waitstate) {
1170+ bool was_waiting_for_user = state_.waiting_for_user ;
11401171 state_.waitstate = waitstate;
11411172 last_waitstate_update_ = std::chrono::steady_clock::now ();
11421173 auto tokens = split_ws (waitstate);
@@ -1149,6 +1180,17 @@ class SeqProgressGui {
11491180
11501181 state_.waiting_for_tcsop = has_tcsop;
11511182 state_.waiting_for_user = has_user;
1183+ if (has_user && !was_waiting_for_user) {
1184+ // New USER gate: default to CONTINUE until explicit gate action is polled.
1185+ state_.user_gate_action = " NONE" ;
1186+ state_.user_gate_action_polled = false ;
1187+ request_snapshot ();
1188+ }
1189+ if (!has_user) {
1190+ // USER gate action applies only while WAITSTATE includes USER.
1191+ state_.user_gate_action = " NONE" ;
1192+ state_.user_gate_action_polled = false ;
1193+ }
11521194
11531195 if (!has_tcsop && (has_acquire || has_guide || has_expose || has_readout || has_user)) {
11541196 state_.ontarget = true ;
@@ -1285,21 +1327,16 @@ class SeqProgressGui {
12851327 state_.last_ontarget = std::chrono::steady_clock::now ();
12861328 }
12871329 if (msg.find (" NOTICE: waiting for USER" ) != std::string::npos) {
1288- state_.waiting_for_user = true ;
1289- // Determine what "continue" will do based on the wait message
1290- state_.continue_starts_acquisition = (msg.find (" start acquisition" ) != std::string::npos);
1291- state_.continue_will_expose = !state_.continue_starts_acquisition ;
1330+ // USER gate intent is driven by seq_waitstate + seq_progress.user_gate_action.
1331+ // Ignore async NOTICE text so UDP/non-ZMQ paths cannot override ZMQ truth.
12921332 // Detect if this is a failure-based user wait
12931333 if (msg.find (" guiding failed" ) != std::string::npos ||
12941334 msg.find (" fine tune failed" ) != std::string::npos) {
12951335 state_.user_wait_after_failure = true ;
12961336 }
12971337 }
12981338 if (msg.find (" NOTICE: received continue" ) != std::string::npos) {
1299- state_.waiting_for_user = false ;
13001339 state_.user_wait_after_failure = false ;
1301- state_.continue_will_expose = false ;
1302- state_.continue_starts_acquisition = false ;
13031340 }
13041341 if (msg.find (" NOTICE: waiting for ACAM guiding" ) != std::string::npos) {
13051342 state_.guiding_on = false ;
0 commit comments