mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
forgetest: cloud tests stay in cloud mode; the page can ignore prerequisites
The cloud job tests (lid-abort, lid-during-button-wait, hunt-lid-open,
pause-resume) run in cloud mode and leave the machine there: enter_cloud
reuses a live session (pid-scoped from the client's own websocket state
lines) and switches once from GRBL mode, declaring the change to the
baseline; nothing switches back. Each test judges the log from its own
window, the print by its own "print [id]: finished" line, and waits the
service's follow-up moves out (wait_quiet) before it ends. hunt-lid-open
restarts the cloud client through the supervisor's stop/start lever for
a fresh connect. The former switch-back is what failed the last bench run
of hunt-lid-open (409 machine is not idle: the service was still
re-finding the head after the lid closed).
The baseline is mode-aware: in cloud mode the client owns the GRBL
controller's init values, the lid lamp, and the position counters; the
mode itself is preserved unless the run declared the change
(Context.mode_changed); controller_mode is never handed back as a bare
setting (a bare write left the persisted mode out of step with the live
one). The undeclared-change restore now uses the captured state, which
the post pass never saw before.
The acceptance page gets an "Ignore prerequisites" switch (remembered by
the browser): POST /start {ignore_requires} starts a test whose requires
are unmet, and the run records the unmet prerequisites in its evidence
and log; they stay required for the release. The cloud tests' requires
no longer chain through cloud.mode-switch.
Proof: tests/test_cloud_suite.py replays the four tests on the bench's
own gfcloud excerpts (fixtures/) and the run loop's emitted pause/resume
lines under the real runner Context against a fake forgectrl; baseline
mode tests and the server override test; the whole suite (98) and the
coverage lint pass. Catalog consequence: cloud.* fingerprints move with
the module; the catalog hash moves with the requires.
This commit is contained in:
+41
@@ -0,0 +1,41 @@
|
||||
2026-08-17T09:44:00.417337+00:00 gfcloud[1927] INFO cnc:set_step_freq 28160
|
||||
2026-08-17T09:44:00.418627+00:00 gfcloud[1927] INFO cnc:set_x_decay 1
|
||||
2026-08-17T09:44:00.419858+00:00 gfcloud[1927] INFO cnc:set_x_mode 8
|
||||
2026-08-17T09:44:00.421083+00:00 gfcloud[1927] INFO cnc:set_x_current 135
|
||||
2026-08-17T09:44:00.422535+00:00 gfcloud[1927] INFO cnc:set_y_decay 1
|
||||
2026-08-17T09:44:00.423794+00:00 gfcloud[1927] INFO cnc:set_y_mode 8
|
||||
2026-08-17T09:44:00.425014+00:00 gfcloud[1927] INFO cnc:set_y_current 22
|
||||
2026-08-17T09:44:00.432591+00:00 gfcloud[1927] INFO machine:_run_loop starting run
|
||||
2026-08-17T09:44:00.434005+00:00 gfcloud[1927] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:44:00.438277+00:00 gfcloud[1927] INFO machine:_run_loop current state: MachineState.RUNNING
|
||||
2026-08-17T09:44:01.785230+00:00 gfcloud[1927] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:44:01.797728+00:00 gfcloud[1927] INFO machine:_run_loop finished run
|
||||
2026-08-17T09:44:01.800661+00:00 gfcloud[1927] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:44:01.808504+00:00 gfcloud[1927] INFO machine:_motion_locked end positions (actual/expected): X (-10313/-10313), Y (-649/-649), Z (0/0)
|
||||
2026-08-17T09:44:01.809326+00:00 gfcloud[1927] INFO machine:_motion_locked motion bytes actual:34952, expected: 34952
|
||||
2026-08-17T09:44:01.810066+00:00 gfcloud[1927] INFO machine:_motion_locked start idle
|
||||
2026-08-17T09:44:01.811352+00:00 gfcloud[1927] INFO cnc:set_x_current 33
|
||||
2026-08-17T09:44:01.812874+00:00 gfcloud[1927] INFO cnc:set_y_current 5
|
||||
2026-08-17T09:44:01.820631+00:00 gfcloud[1927] INFO machine:_motion_locked end positions (-10313, -649, 0)
|
||||
2026-08-17T09:44:01.822062+00:00 gfcloud[1927] INFO machine:_motion end motion
|
||||
2026-08-17T09:44:01.822829+00:00 gfcloud[1927] INFO basemachine:_finish_action motion [1576550614]: finished with event ":completed"
|
||||
2026-08-17T09:44:01.824200+00:00 gfcloud[1927] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:44:07.993647+00:00 gfcloud[1927] INFO gfuiservice:run service action request: print (ready)
|
||||
2026-08-17T09:44:07.998655+00:00 gfcloud[1927] INFO machine:_motion start motion
|
||||
2026-08-17T09:44:07.999643+00:00 gfcloud[1927] INFO gfuiservice:run print (ready) service action print
|
||||
2026-08-17T09:44:14.199666+00:00 gfcloud[1927] INFO cnc:laser_latch 0
|
||||
2026-08-17T09:44:14.208291+00:00 gfcloud[1927] INFO machine:_button_wait waiting for button
|
||||
2026-08-17T09:44:21.270846+00:00 gfcloud[1927] INFO machine:_switch_event lid opened
|
||||
2026-08-17T09:44:21.275703+00:00 gfcloud[1927] WARNING machine:_button_wait button wait lid opened - relocking the laser
|
||||
2026-08-17T09:44:21.278615+00:00 gfcloud[1927] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:44:21.288305+00:00 gfcloud[1927] INFO machine:_return_home start return home
|
||||
2026-08-17T09:44:21.291373+00:00 gfcloud[1927] INFO machine:_return_home return home: already at the job start
|
||||
2026-08-17T09:44:21.292501+00:00 gfcloud[1927] INFO machine:_motion_locked start cool down
|
||||
2026-08-17T09:44:21.305578+00:00 gfcloud[1927] INFO machine:_motion_locked start idle
|
||||
2026-08-17T09:44:21.307322+00:00 gfcloud[1927] INFO cnc:set_x_current 33
|
||||
2026-08-17T09:44:21.309185+00:00 gfcloud[1927] INFO cnc:set_y_current 5
|
||||
2026-08-17T09:44:21.316340+00:00 gfcloud[1927] INFO machine:_motion_locked end positions (0, 0, 0)
|
||||
2026-08-17T09:44:21.318139+00:00 gfcloud[1927] INFO machine:_motion end motion
|
||||
2026-08-17T09:44:21.318860+00:00 gfcloud[1927] INFO basemachine:_finish_action print [1576550621]: finished with event ":cancelled"
|
||||
2026-08-17T09:44:21.320214+00:00 gfcloud[1927] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:44:34.058320+00:00 gfcloud[1927] INFO machine:_switch_event lid closed
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
2026-08-17T09:44:35.789556+00:00 gfcloud[1927] INFO websocket:img_upload START
|
||||
2026-08-17T09:44:36.105516+00:00 gfcloud[1927] INFO websocket:img_upload COMPLETE
|
||||
2026-08-17T09:44:36.109421+00:00 gfcloud[1927] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:44:38.461442+00:00 gfcloud[1927] INFO gfcloud:_shutdown shutdown requested
|
||||
2026-08-17T09:44:38.910494+00:00 gfcloud[1927] INFO machine:_shutdown shutting down
|
||||
2026-08-17T09:44:38.913049+00:00 gfcloud[1927] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:44:38.950354+00:00 gfcloud[1927] INFO machine:_shutdown joining switch thread
|
||||
2026-08-17T09:44:38.956909+00:00 gfcloud[1927] INFO machine:_shutdown shut down complete
|
||||
2026-08-17T09:44:38.986287+00:00 gfcloud[1927] INFO websocket:_on_close RX-EVENT: closed (None, None)
|
||||
2026-08-17T09:44:38.989003+00:00 gfcloud[1927] INFO websocket:run CLOSING
|
||||
2026-08-17T09:44:38.992724+00:00 gfcloud[1927] INFO gfcloud:main gfcloud exit
|
||||
2026-08-17T09:45:08.449553+00:00 gfcloud[2278] INFO gfuiservice:__init__ INITIALIZED
|
||||
2026-08-17T09:45:08.451929+00:00 gfcloud[2278] INFO authentication:authenticate_machine START
|
||||
2026-08-17T09:45:08.965893+00:00 gfcloud[2278] INFO authentication:authenticate_machine SUCCESS
|
||||
2026-08-17T09:45:08.967947+00:00 gfcloud[2278] INFO websocket:ws_connect CONNECTING
|
||||
2026-08-17T09:45:09.421918+00:00 gfcloud[2278] INFO _logging:info Websocket connected
|
||||
2026-08-17T09:45:09.424224+00:00 gfcloud[2278] INFO websocket:_on_open RX-EVENT: ready
|
||||
2026-08-17T09:45:09.973615+00:00 gfcloud[2278] INFO websocket:ws_connect ESTABLISHED
|
||||
2026-08-17T09:45:09.994287+00:00 gfcloud[2278] INFO cnc:set_ignored_faults 0
|
||||
2026-08-17T09:45:09.996281+00:00 gfcloud[2278] INFO cnc:set_step_freq 10000
|
||||
2026-08-17T09:45:09.998803+00:00 gfcloud[2278] INFO cnc:set_x_mode Microstep.M_8
|
||||
2026-08-17T09:45:09.999688+00:00 gfcloud[2278] INFO cnc:set_y_mode Microstep.M_8
|
||||
2026-08-17T09:45:10.000483+00:00 gfcloud[2278] INFO cnc:set_x_decay 1
|
||||
2026-08-17T09:45:10.001229+00:00 gfcloud[2278] INFO cnc:set_y_decay 1
|
||||
2026-08-17T09:45:10.002000+00:00 gfcloud[2278] INFO cnc:set_x_current 33
|
||||
2026-08-17T09:45:10.007267+00:00 gfcloud[2278] INFO cnc:set_y_current 33
|
||||
2026-08-17T09:45:10.008695+00:00 gfcloud[2278] INFO z_axis:reset resetting z
|
||||
2026-08-17T09:45:10.009124+00:00 gfcloud[2278] INFO z_axis:configure configuring z_enable: False, current: ZCur.LOW, mode: Microstep.HALF
|
||||
2026-08-17T09:45:10.021592+00:00 gfcloud[2278] INFO gfuiservice:run service action request: settings (ready)
|
||||
2026-08-17T09:45:10.024928+00:00 gfcloud[2278] INFO settings:send_report START
|
||||
2026-08-17T09:45:10.039856+00:00 gfcloud[2278] INFO settings:send_report COMPLETE
|
||||
2026-08-17T09:45:10.040650+00:00 gfcloud[2278] INFO gfuiservice:run settings (ready) service action dispatched
|
||||
2026-08-17T09:45:10.591813+00:00 gfcloud[2278] INFO gfuiservice:run service action request: hunt (ready)
|
||||
2026-08-17T09:45:10.596213+00:00 gfcloud[2278] INFO z_axis:home starting z homing cycle
|
||||
2026-08-17T09:45:10.599109+00:00 gfcloud[2278] INFO gfuiservice:run hunt (ready) service action hunt
|
||||
2026-08-17T09:45:10.600433+00:00 gfcloud[2278] INFO z_axis:configure configuring z_enable: True, current: ZCur.HIGH, mode: Microstep.FULL
|
||||
2026-08-17T09:45:10.607559+00:00 gfcloud[2278] INFO z_axis:_step_until starting z_step until at_home: False
|
||||
2026-08-17T09:45:11.160422+00:00 gfcloud[2278] INFO z_axis:_step_until took -3 z_steps
|
||||
2026-08-17T09:45:11.161195+00:00 gfcloud[2278] INFO z_axis:_step_until starting z_step until at_home: True
|
||||
2026-08-17T09:45:11.714663+00:00 gfcloud[2278] INFO z_axis:_step_until took 3 z_steps
|
||||
2026-08-17T09:45:11.717907+00:00 gfcloud[2278] INFO z_axis:home pass: 1
|
||||
2026-08-17T09:45:11.719134+00:00 gfcloud[2278] INFO z_axis:_step_until starting z_step until at_home: False
|
||||
2026-08-17T09:45:12.272747+00:00 gfcloud[2278] INFO z_axis:_step_until took -3 z_steps
|
||||
2026-08-17T09:45:12.274301+00:00 gfcloud[2278] INFO z_axis:_step_until starting z_step until at_home: True
|
||||
2026-08-17T09:45:12.829585+00:00 gfcloud[2278] INFO z_axis:_step_until took 3 z_steps
|
||||
2026-08-17T09:45:12.831305+00:00 gfcloud[2278] INFO z_axis:home pass: 2
|
||||
2026-08-17T09:45:12.832786+00:00 gfcloud[2278] INFO z_axis:_step_until starting z_step until at_home: False
|
||||
2026-08-17T09:45:13.385567+00:00 gfcloud[2278] INFO z_axis:_step_until took -3 z_steps
|
||||
2026-08-17T09:45:13.397106+00:00 gfcloud[2278] INFO z_axis:_step_until starting z_step until at_home: True
|
||||
2026-08-17T09:45:13.947958+00:00 gfcloud[2278] INFO z_axis:_step_until took 3 z_steps
|
||||
2026-08-17T09:45:13.949754+00:00 gfcloud[2278] INFO z_axis:home pass: 3
|
||||
2026-08-17T09:45:13.951244+00:00 gfcloud[2278] INFO z_axis:_step_until starting z_step until at_home: False
|
||||
2026-08-17T09:45:14.504840+00:00 gfcloud[2278] INFO z_axis:_step_until took -3 z_steps
|
||||
2026-08-17T09:45:14.506743+00:00 gfcloud[2278] INFO z_axis:_step_until starting z_step until at_home: True
|
||||
2026-08-17T09:45:15.060854+00:00 gfcloud[2278] INFO z_axis:_step_until took 3 z_steps
|
||||
2026-08-17T09:45:15.062536+00:00 gfcloud[2278] INFO z_axis:home pass: 4
|
||||
2026-08-17T09:45:15.063941+00:00 gfcloud[2278] INFO z_axis:_step_until starting z_step until at_home: False
|
||||
2026-08-17T09:45:15.617907+00:00 gfcloud[2278] INFO z_axis:_step_until took -3 z_steps
|
||||
2026-08-17T09:45:15.619505+00:00 gfcloud[2278] INFO z_axis:_step_until starting z_step until at_home: True
|
||||
2026-08-17T09:45:16.175270+00:00 gfcloud[2278] INFO z_axis:_step_until took 3 z_steps
|
||||
2026-08-17T09:45:16.177421+00:00 gfcloud[2278] INFO z_axis:home pass: 5
|
||||
2026-08-17T09:45:16.179808+00:00 gfcloud[2278] INFO z_axis:_step_until starting z_step until at_home: False
|
||||
2026-08-17T09:45:16.733698+00:00 gfcloud[2278] INFO z_axis:_step_until took -3 z_steps
|
||||
2026-08-17T09:45:16.735355+00:00 gfcloud[2278] INFO z_axis:_step_until starting z_step until at_home: True
|
||||
2026-08-17T09:45:17.289377+00:00 gfcloud[2278] INFO z_axis:_step_until took 3 z_steps
|
||||
2026-08-17T09:45:17.291109+00:00 gfcloud[2278] INFO z_axis:configure configuring z_enable: None, current: ZCur.LOW, mode: Microstep.HALF
|
||||
2026-08-17T09:45:17.296301+00:00 gfcloud[2278] INFO z_axis:home homing cycle complete: [0, 0, 0, 0, 0]
|
||||
2026-08-17T09:45:17.298209+00:00 gfcloud[2278] INFO machine:_motion start motion
|
||||
2026-08-17T09:45:17.968494+00:00 gfcloud[2278] INFO cnc:set_step_freq 10000
|
||||
2026-08-17T09:45:17.969759+00:00 gfcloud[2278] INFO cnc:set_x_decay 1
|
||||
2026-08-17T09:45:17.970993+00:00 gfcloud[2278] INFO cnc:set_x_mode 8
|
||||
2026-08-17T09:45:17.972200+00:00 gfcloud[2278] INFO cnc:set_x_current 135
|
||||
2026-08-17T09:45:17.973616+00:00 gfcloud[2278] INFO cnc:set_y_decay 1
|
||||
2026-08-17T09:45:17.974870+00:00 gfcloud[2278] INFO cnc:set_y_mode 8
|
||||
2026-08-17T09:45:17.976077+00:00 gfcloud[2278] INFO cnc:set_y_current 22
|
||||
2026-08-17T09:45:17.983820+00:00 gfcloud[2278] INFO machine:_run_loop starting run
|
||||
2026-08-17T09:45:17.985159+00:00 gfcloud[2278] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:45:17.989360+00:00 gfcloud[2278] INFO machine:_run_loop current state: MachineState.RUNNING
|
||||
2026-08-17T09:45:19.630807+00:00 gfcloud[2278] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:45:19.636283+00:00 gfcloud[2278] INFO machine:_run_loop finished run
|
||||
2026-08-17T09:45:19.637979+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:45:19.651159+00:00 gfcloud[2278] INFO machine:_motion_locked end positions (actual/expected): X (0/0), Y (0/0), Z (-4/-4)
|
||||
2026-08-17T09:45:19.652103+00:00 gfcloud[2278] INFO machine:_motion_locked motion bytes actual:15874, expected: 15874
|
||||
2026-08-17T09:45:19.652852+00:00 gfcloud[2278] INFO machine:_motion_locked start idle
|
||||
2026-08-17T09:45:19.653964+00:00 gfcloud[2278] INFO cnc:set_x_current 33
|
||||
2026-08-17T09:45:19.655305+00:00 gfcloud[2278] INFO cnc:set_y_current 5
|
||||
2026-08-17T09:45:19.664032+00:00 gfcloud[2278] INFO machine:_motion_locked end positions (0, 0, -4)
|
||||
2026-08-17T09:45:19.664853+00:00 gfcloud[2278] INFO machine:_motion end motion
|
||||
2026-08-17T09:45:19.665575+00:00 gfcloud[2278] INFO basemachine:_finish_action hunt [1576550660]: finished with event ":completed"
|
||||
2026-08-17T09:45:19.667114+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:45:20.027338+00:00 gfcloud[2278] INFO gfuiservice:run service action request: lid_image (ready)
|
||||
2026-08-17T09:45:20.030024+00:00 gfcloud[2278] INFO ffmachine:_lid_image capturing Lid Image via forgectrl
|
||||
2026-08-17T09:45:20.040337+00:00 gfcloud[2278] INFO gfuiservice:run lid_image (ready) service action dispatched
|
||||
2026-08-17T09:45:21.195560+00:00 gfcloud[2278] INFO ffmachine:_lid_image uploading Lid Image
|
||||
2026-08-17T09:45:21.196529+00:00 gfcloud[2278] INFO websocket:img_upload START
|
||||
2026-08-17T09:45:21.721382+00:00 gfcloud[2278] INFO websocket:img_upload COMPLETE
|
||||
2026-08-17T09:45:21.725394+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:45:23.732857+00:00 gfcloud[2278] INFO gfuiservice:run service action request: motion (ready)
|
||||
2026-08-17T09:45:23.736044+00:00 gfcloud[2278] INFO machine:_motion start motion
|
||||
2026-08-17T09:45:23.737995+00:00 gfcloud[2278] INFO gfuiservice:run motion (ready) service action motion
|
||||
2026-08-17T09:45:23.738941+00:00 gfcloud[2278] INFO machine:_safe_to_move lid opened, unsafe to move
|
||||
2026-08-17T09:45:23.739814+00:00 gfcloud[2278] INFO machine:_motion end motion
|
||||
2026-08-17T09:45:23.740605+00:00 gfcloud[2278] INFO basemachine:_finish_action motion [1576550667]: finished with event ":cancelled"
|
||||
2026-08-17T09:45:23.742169+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:45:42.629674+00:00 gfcloud[2278] INFO machine:_switch_event lid closed
|
||||
2026-08-17T09:45:42.920194+00:00 gfcloud[2278] INFO gfuiservice:run service action request: lid_image (ready)
|
||||
2026-08-17T09:45:42.924152+00:00 gfcloud[2278] INFO ffmachine:_lid_image capturing Lid Image via forgectrl
|
||||
2026-08-17T09:45:42.927934+00:00 gfcloud[2278] INFO gfuiservice:run lid_image (ready) service action dispatched
|
||||
2026-08-17T09:45:43.989152+00:00 gfcloud[2278] INFO ffmachine:_lid_image uploading Lid Image
|
||||
2026-08-17T09:45:43.990022+00:00 gfcloud[2278] INFO websocket:img_upload START
|
||||
2026-08-17T09:45:44.312060+00:00 gfcloud[2278] INFO websocket:img_upload COMPLETE
|
||||
2026-08-17T09:45:44.315952+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:45:46.652633+00:00 gfcloud[2278] INFO gfuiservice:run service action request: motion (ready)
|
||||
2026-08-17T09:45:46.654950+00:00 gfcloud[2278] INFO machine:_motion start motion
|
||||
2026-08-17T09:45:46.657522+00:00 gfcloud[2278] INFO gfuiservice:run motion (ready) service action motion
|
||||
2026-08-17T09:45:48.089456+00:00 gfcloud[2278] INFO cnc:set_step_freq 10000
|
||||
2026-08-17T09:45:48.090706+00:00 gfcloud[2278] INFO cnc:set_x_decay 1
|
||||
2026-08-17T09:45:48.091970+00:00 gfcloud[2278] INFO cnc:set_x_mode 8
|
||||
2026-08-17T09:45:48.093155+00:00 gfcloud[2278] INFO cnc:set_x_current 135
|
||||
2026-08-17T09:45:48.094587+00:00 gfcloud[2278] INFO cnc:set_y_decay 1
|
||||
2026-08-17T09:45:48.095866+00:00 gfcloud[2278] INFO cnc:set_y_mode 8
|
||||
2026-08-17T09:45:48.097182+00:00 gfcloud[2278] INFO cnc:set_y_current 22
|
||||
2026-08-17T09:45:48.104758+00:00 gfcloud[2278] INFO machine:_run_loop starting run
|
||||
2026-08-17T09:45:48.106128+00:00 gfcloud[2278] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:45:48.110477+00:00 gfcloud[2278] INFO machine:_run_loop current state: MachineState.RUNNING
|
||||
2026-08-17T09:45:50.393091+00:00 gfcloud[2278] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:45:50.398748+00:00 gfcloud[2278] INFO machine:_run_loop finished run
|
||||
2026-08-17T09:45:50.399629+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:45:50.423949+00:00 gfcloud[2278] INFO machine:_motion_locked end positions (actual/expected): X (12939/12939), Y (7402/7402), Z (0/0)
|
||||
2026-08-17T09:45:50.425534+00:00 gfcloud[2278] INFO machine:_motion_locked motion bytes actual:22740, expected: 22740
|
||||
2026-08-17T09:45:50.426258+00:00 gfcloud[2278] INFO machine:_motion_locked start idle
|
||||
2026-08-17T09:45:50.427835+00:00 gfcloud[2278] INFO cnc:set_x_current 33
|
||||
2026-08-17T09:45:50.429387+00:00 gfcloud[2278] INFO cnc:set_y_current 5
|
||||
2026-08-17T09:45:50.437659+00:00 gfcloud[2278] INFO machine:_motion_locked end positions (12939, 7402, 0)
|
||||
2026-08-17T09:45:50.438474+00:00 gfcloud[2278] INFO machine:_motion end motion
|
||||
2026-08-17T09:45:50.439167+00:00 gfcloud[2278] INFO basemachine:_finish_action motion [1576550679]: finished with event ":completed"
|
||||
2026-08-17T09:45:50.440481+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:45:50.770056+00:00 gfcloud[2278] INFO gfuiservice:run service action request: lid_image (ready)
|
||||
2026-08-17T09:45:50.772800+00:00 gfcloud[2278] INFO ffmachine:_lid_image capturing Lid Image via forgectrl
|
||||
2026-08-17T09:45:50.782017+00:00 gfcloud[2278] INFO gfuiservice:run lid_image (ready) service action dispatched
|
||||
2026-08-17T09:45:51.427848+00:00 gfcloud[2278] INFO ffmachine:_lid_image uploading Lid Image
|
||||
2026-08-17T09:45:51.428456+00:00 gfcloud[2278] INFO websocket:img_upload START
|
||||
2026-08-17T09:45:51.757479+00:00 gfcloud[2278] INFO websocket:img_upload COMPLETE
|
||||
2026-08-17T09:45:51.760692+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:45:54.222123+00:00 gfcloud[2278] INFO gfuiservice:run service action request: motion (ready)
|
||||
2026-08-17T09:45:54.224786+00:00 gfcloud[2278] INFO machine:_motion start motion
|
||||
2026-08-17T09:45:54.226855+00:00 gfcloud[2278] INFO gfuiservice:run motion (ready) service action motion
|
||||
2026-08-17T09:45:54.557705+00:00 gfcloud[2278] INFO cnc:set_step_freq 10000
|
||||
2026-08-17T09:45:54.562210+00:00 gfcloud[2278] INFO cnc:set_x_decay 1
|
||||
2026-08-17T09:45:54.571212+00:00 gfcloud[2278] INFO cnc:set_x_mode 8
|
||||
2026-08-17T09:45:54.573373+00:00 gfcloud[2278] INFO cnc:set_x_current 135
|
||||
2026-08-17T09:45:54.596668+00:00 gfcloud[2278] INFO cnc:set_y_decay 1
|
||||
2026-08-17T09:45:54.597612+00:00 gfcloud[2278] INFO cnc:set_y_mode 8
|
||||
2026-08-17T09:45:54.598479+00:00 gfcloud[2278] INFO cnc:set_y_current 22
|
||||
2026-08-17T09:45:54.622488+00:00 gfcloud[2278] INFO machine:_run_loop starting run
|
||||
2026-08-17T09:45:54.624223+00:00 gfcloud[2278] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:45:54.629307+00:00 gfcloud[2278] INFO machine:_run_loop current state: MachineState.RUNNING
|
||||
2026-08-17T09:45:54.835600+00:00 gfcloud[2278] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:45:54.838848+00:00 gfcloud[2278] INFO machine:_run_loop finished run
|
||||
2026-08-17T09:45:54.839726+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:45:54.849011+00:00 gfcloud[2278] INFO machine:_motion_locked end positions (actual/expected): X (159/159), Y (5/5), Z (0/0)
|
||||
2026-08-17T09:45:54.849946+00:00 gfcloud[2278] INFO machine:_motion_locked motion bytes actual:1222, expected: 1222
|
||||
2026-08-17T09:45:54.850790+00:00 gfcloud[2278] INFO machine:_motion_locked start idle
|
||||
2026-08-17T09:45:54.852317+00:00 gfcloud[2278] INFO cnc:set_x_current 33
|
||||
2026-08-17T09:45:54.854073+00:00 gfcloud[2278] INFO cnc:set_y_current 5
|
||||
2026-08-17T09:45:54.880450+00:00 gfcloud[2278] INFO machine:_motion_locked end positions (159, 5, 0)
|
||||
2026-08-17T09:45:54.881925+00:00 gfcloud[2278] INFO machine:_motion end motion
|
||||
2026-08-17T09:45:54.882641+00:00 gfcloud[2278] INFO basemachine:_finish_action motion [1576550683]: finished with event ":completed"
|
||||
2026-08-17T09:45:54.884500+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:45:55.137070+00:00 gfcloud[2278] INFO gfuiservice:run service action request: lid_image (ready)
|
||||
2026-08-17T09:45:55.139449+00:00 gfcloud[2278] INFO ffmachine:_lid_image capturing Lid Image via forgectrl
|
||||
2026-08-17T09:45:55.142346+00:00 gfcloud[2278] INFO gfuiservice:run lid_image (ready) service action dispatched
|
||||
2026-08-17T09:45:55.809276+00:00 gfcloud[2278] INFO ffmachine:_lid_image uploading Lid Image
|
||||
2026-08-17T09:45:55.810155+00:00 gfcloud[2278] INFO websocket:img_upload START
|
||||
2026-08-17T09:45:56.158888+00:00 gfcloud[2278] INFO websocket:img_upload COMPLETE
|
||||
2026-08-17T09:45:56.162644+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:45:58.467590+00:00 gfcloud[2278] INFO gfuiservice:run service action request: motion (ready)
|
||||
2026-08-17T09:45:58.469426+00:00 gfcloud[2278] INFO machine:_motion start motion
|
||||
2026-08-17T09:45:58.471091+00:00 gfcloud[2278] INFO gfuiservice:run motion (ready) service action motion
|
||||
2026-08-17T09:46:00.721960+00:00 gfcloud[2278] INFO cnc:set_step_freq 28160
|
||||
2026-08-17T09:46:00.725688+00:00 gfcloud[2278] INFO cnc:set_x_decay 1
|
||||
2026-08-17T09:46:00.727477+00:00 gfcloud[2278] INFO cnc:set_x_mode 8
|
||||
2026-08-17T09:46:00.728738+00:00 gfcloud[2278] INFO cnc:set_x_current 135
|
||||
2026-08-17T09:46:00.730198+00:00 gfcloud[2278] INFO cnc:set_y_decay 1
|
||||
2026-08-17T09:46:00.731506+00:00 gfcloud[2278] INFO cnc:set_y_mode 8
|
||||
2026-08-17T09:46:00.732735+00:00 gfcloud[2278] INFO cnc:set_y_current 22
|
||||
2026-08-17T09:46:00.740459+00:00 gfcloud[2278] INFO machine:_run_loop starting run
|
||||
2026-08-17T09:46:00.741865+00:00 gfcloud[2278] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:46:00.745983+00:00 gfcloud[2278] INFO machine:_run_loop current state: MachineState.RUNNING
|
||||
2026-08-17T09:46:02.491897+00:00 gfcloud[2278] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:46:02.494137+00:00 gfcloud[2278] INFO machine:_run_loop finished run
|
||||
2026-08-17T09:46:02.495155+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:46:02.530565+00:00 gfcloud[2278] INFO machine:_motion_locked end positions (actual/expected): X (-13096/-13096), Y (-7400/-7400), Z (0/0)
|
||||
2026-08-17T09:46:02.531413+00:00 gfcloud[2278] INFO machine:_motion_locked motion bytes actual:47243, expected: 47243
|
||||
2026-08-17T09:46:02.538168+00:00 gfcloud[2278] INFO machine:_motion_locked start idle
|
||||
2026-08-17T09:46:02.539603+00:00 gfcloud[2278] INFO cnc:set_x_current 33
|
||||
2026-08-17T09:46:02.541147+00:00 gfcloud[2278] INFO cnc:set_y_current 5
|
||||
2026-08-17T09:46:02.549578+00:00 gfcloud[2278] INFO machine:_motion_locked end positions (-13096, -7400, 0)
|
||||
2026-08-17T09:46:02.550385+00:00 gfcloud[2278] INFO machine:_motion end motion
|
||||
2026-08-17T09:46:02.551153+00:00 gfcloud[2278] INFO basemachine:_finish_action motion [1576550685]: finished with event ":completed"
|
||||
2026-08-17T09:46:02.552496+00:00 gfcloud[2278] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:46:03.007623+00:00 gfcloud[2278] INFO gfuiservice:run service action request: lid_image (ready)
|
||||
2026-08-17T09:46:03.009488+00:00 gfcloud[2278] INFO ffmachine:_lid_image capturing Lid Image via forgectrl
|
||||
2026-08-17T09:46:03.037155+00:00 gfcloud[2278] INFO gfuiservice:run lid_image (ready) service action dispatched
|
||||
2026-08-17T09:46:03.758520+00:00 gfcloud[2278] INFO ffmachine:_lid_image uploading Lid Image
|
||||
2026-08-17T09:46:03.760049+00:00 gfcloud[2278] INFO websocket:img_upload START
|
||||
2026-08-17T09:46:04.128126+00:00 gfcloud[2278] INFO websocket:img_upload COMPLETE
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
2026-08-17T09:42:09.781500+00:00 gfcloud[1522] INFO gfuiservice:run motion (ready) service action motion
|
||||
2026-08-17T09:42:11.281337+00:00 gfcloud[1522] INFO cnc:set_step_freq 28160
|
||||
2026-08-17T09:42:11.282627+00:00 gfcloud[1522] INFO cnc:set_x_decay 1
|
||||
2026-08-17T09:42:11.283850+00:00 gfcloud[1522] INFO cnc:set_x_mode 8
|
||||
2026-08-17T09:42:11.285121+00:00 gfcloud[1522] INFO cnc:set_x_current 135
|
||||
2026-08-17T09:42:11.286711+00:00 gfcloud[1522] INFO cnc:set_y_decay 1
|
||||
2026-08-17T09:42:11.287977+00:00 gfcloud[1522] INFO cnc:set_y_mode 8
|
||||
2026-08-17T09:42:11.289235+00:00 gfcloud[1522] INFO cnc:set_y_current 22
|
||||
2026-08-17T09:42:11.297425+00:00 gfcloud[1522] INFO machine:_run_loop starting run
|
||||
2026-08-17T09:42:11.299141+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:42:11.303264+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.RUNNING
|
||||
2026-08-17T09:42:12.560917+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:42:12.572022+00:00 gfcloud[1522] INFO machine:_run_loop finished run
|
||||
2026-08-17T09:42:12.572811+00:00 gfcloud[1522] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:42:12.580801+00:00 gfcloud[1522] INFO machine:_motion_locked end positions (actual/expected): X (-10313/-10313), Y (-649/-649), Z (0/0)
|
||||
2026-08-17T09:42:12.581620+00:00 gfcloud[1522] INFO machine:_motion_locked motion bytes actual:34952, expected: 34952
|
||||
2026-08-17T09:42:12.582366+00:00 gfcloud[1522] INFO machine:_motion_locked start idle
|
||||
2026-08-17T09:42:12.583540+00:00 gfcloud[1522] INFO cnc:set_x_current 33
|
||||
2026-08-17T09:42:12.584928+00:00 gfcloud[1522] INFO cnc:set_y_current 5
|
||||
2026-08-17T09:42:12.593681+00:00 gfcloud[1522] INFO machine:_motion_locked end positions (-10313, -649, 0)
|
||||
2026-08-17T09:42:12.604394+00:00 gfcloud[1522] INFO machine:_motion end motion
|
||||
2026-08-17T09:42:12.604757+00:00 gfcloud[1522] INFO basemachine:_finish_action motion [1576550503]: finished with event ":completed"
|
||||
2026-08-17T09:42:12.605824+00:00 gfcloud[1522] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:42:15.672518+00:00 gfcloud[1522] INFO gfuiservice:run service action request: print (ready)
|
||||
2026-08-17T09:42:15.676330+00:00 gfcloud[1522] INFO machine:_motion start motion
|
||||
2026-08-17T09:42:15.680419+00:00 gfcloud[1522] INFO gfuiservice:run print (ready) service action print
|
||||
2026-08-17T09:42:21.975587+00:00 gfcloud[1522] INFO cnc:laser_latch 0
|
||||
2026-08-17T09:42:22.001354+00:00 gfcloud[1522] INFO machine:_button_wait waiting for button
|
||||
2026-08-17T09:42:24.774117+00:00 gfcloud[1522] INFO machine:_switch_event button pushed
|
||||
2026-08-17T09:42:24.778509+00:00 gfcloud[1522] INFO cnc:set_step_freq 10000
|
||||
2026-08-17T09:42:24.781114+00:00 gfcloud[1522] INFO cnc:set_x_decay 1
|
||||
2026-08-17T09:42:24.783654+00:00 gfcloud[1522] INFO cnc:set_x_mode 8
|
||||
2026-08-17T09:42:24.786180+00:00 gfcloud[1522] INFO cnc:set_x_current 135
|
||||
2026-08-17T09:42:24.789088+00:00 gfcloud[1522] INFO cnc:set_y_decay 1
|
||||
2026-08-17T09:42:24.791559+00:00 gfcloud[1522] INFO cnc:set_y_mode 8
|
||||
2026-08-17T09:42:24.794380+00:00 gfcloud[1522] INFO cnc:set_y_current 22
|
||||
2026-08-17T09:42:24.815263+00:00 gfcloud[1522] INFO machine:_run_loop starting run
|
||||
2026-08-17T09:42:24.816687+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:42:24.820823+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.RUNNING
|
||||
2026-08-17T09:42:25.049489+00:00 gfcloud[1522] INFO machine:_switch_event button released
|
||||
2026-08-17T09:42:30.832527+00:00 gfcloud[1522] INFO machine:_switch_event lid opened
|
||||
2026-08-17T09:42:30.838627+00:00 gfcloud[1522] WARNING machine:_run_loop lid opened mid-run; stopping motion
|
||||
2026-08-17T09:42:30.941553+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:42:30.947477+00:00 gfcloud[1522] INFO machine:_run_loop finished run
|
||||
2026-08-17T09:42:30.949099+00:00 gfcloud[1522] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:42:30.964916+00:00 gfcloud[1522] INFO machine:_motion_locked end positions (actual/expected): X (8454/7573), Y (684/684), Z (3/3)
|
||||
2026-08-17T09:42:30.965704+00:00 gfcloud[1522] INFO machine:_motion_locked motion bytes actual:60649, expected: 238043
|
||||
2026-08-17T09:42:30.972180+00:00 gfcloud[1522] INFO machine:_return_home start return home
|
||||
2026-08-17T09:42:31.847247+00:00 gfcloud[1522] INFO machine:_run_loop starting run
|
||||
2026-08-17T09:42:31.848617+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:42:31.852977+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.RUNNING
|
||||
2026-08-17T09:42:36.970521+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:42:36.976059+00:00 gfcloud[1522] INFO machine:_run_loop finished run
|
||||
2026-08-17T09:42:36.977738+00:00 gfcloud[1522] INFO machine:_return_home return home complete
|
||||
2026-08-17T09:42:36.979696+00:00 gfcloud[1522] INFO machine:_motion_locked start cool down
|
||||
2026-08-17T09:42:36.998973+00:00 gfcloud[1522] INFO machine:_motion_locked start idle
|
||||
2026-08-17T09:42:37.000314+00:00 gfcloud[1522] INFO cnc:set_x_current 33
|
||||
2026-08-17T09:42:37.001774+00:00 gfcloud[1522] INFO cnc:set_y_current 5
|
||||
2026-08-17T09:42:37.010089+00:00 gfcloud[1522] INFO machine:_motion_locked end positions (0, 0, 3)
|
||||
2026-08-17T09:42:37.010885+00:00 gfcloud[1522] INFO machine:_motion end motion
|
||||
2026-08-17T09:42:37.011563+00:00 gfcloud[1522] INFO basemachine:_finish_action print [1576550507]: finished with event ":cancelled"
|
||||
2026-08-17T09:42:37.013071+00:00 gfcloud[1522] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:42:37.449180+00:00 gfcloud[1522] INFO gfuiservice:run service action request: hunt (ready)
|
||||
2026-08-17T09:42:37.451286+00:00 gfcloud[1522] INFO z_axis:home starting z homing cycle
|
||||
2026-08-17T09:42:37.452782+00:00 gfcloud[1522] INFO gfuiservice:run hunt (ready) service action hunt
|
||||
2026-08-17T09:42:37.453691+00:00 gfcloud[1522] INFO z_axis:configure configuring z_enable: True, current: ZCur.HIGH, mode: Microstep.FULL
|
||||
2026-08-17T09:42:37.458283+00:00 gfcloud[1522] INFO z_axis:_step_until starting z_step until at_home: False
|
||||
2026-08-17T09:42:37.827610+00:00 gfcloud[1522] INFO z_axis:_step_until took -2 z_steps
|
||||
2026-08-17T09:42:37.829278+00:00 gfcloud[1522] INFO z_axis:_step_until starting z_step until at_home: True
|
||||
2026-08-17T09:42:38.200493+00:00 gfcloud[1522] INFO z_axis:_step_until took 2 z_steps
|
||||
2026-08-17T09:42:38.202234+00:00 gfcloud[1522] INFO z_axis:home pass: 1
|
||||
2026-08-17T09:42:38.203788+00:00 gfcloud[1522] INFO z_axis:_step_until starting z_step until at_home: False
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
2026-08-17T09:42:15.672518+00:00 gfcloud[1522] INFO gfuiservice:run service action request: print (ready)
|
||||
2026-08-17T09:42:15.676330+00:00 gfcloud[1522] INFO machine:_motion start motion
|
||||
2026-08-17T09:42:15.680419+00:00 gfcloud[1522] INFO gfuiservice:run print (ready) service action print
|
||||
2026-08-17T09:42:21.975587+00:00 gfcloud[1522] INFO cnc:laser_latch 0
|
||||
2026-08-17T09:42:22.001354+00:00 gfcloud[1522] INFO machine:_button_wait waiting for button
|
||||
2026-08-17T09:42:24.774117+00:00 gfcloud[1522] INFO machine:_switch_event button pushed
|
||||
2026-08-17T09:42:24.778509+00:00 gfcloud[1522] INFO cnc:set_step_freq 10000
|
||||
2026-08-17T09:42:24.781114+00:00 gfcloud[1522] INFO cnc:set_x_decay 1
|
||||
2026-08-17T09:42:24.783654+00:00 gfcloud[1522] INFO cnc:set_x_mode 8
|
||||
2026-08-17T09:42:24.786180+00:00 gfcloud[1522] INFO cnc:set_x_current 135
|
||||
2026-08-17T09:42:24.789088+00:00 gfcloud[1522] INFO cnc:set_y_decay 1
|
||||
2026-08-17T09:42:24.791559+00:00 gfcloud[1522] INFO cnc:set_y_mode 8
|
||||
2026-08-17T09:42:24.794380+00:00 gfcloud[1522] INFO cnc:set_y_current 22
|
||||
2026-08-17T09:42:24.815263+00:00 gfcloud[1522] INFO machine:_run_loop starting run
|
||||
2026-08-17T09:42:24.816687+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:42:24.820823+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.RUNNING
|
||||
2026-08-17T09:42:25.049489+00:00 gfcloud[1522] INFO machine:_switch_event button released
|
||||
2026-08-17T09:42:31.104201+00:00 gfcloud[1522] INFO machine:_switch_event button pushed
|
||||
2026-08-17T09:42:31.106877+00:00 gfcloud[1522] INFO machine:_run_loop button pressed mid-run; pausing
|
||||
2026-08-17T09:42:31.288114+00:00 gfcloud[1522] INFO machine:_switch_event button released
|
||||
2026-08-17T09:42:31.612040+00:00 gfcloud[1522] INFO machine:_run_loop paused at Position(x=AxisPosition(steps=1210, mm=22.68, inch=0.89), y=AxisPosition(steps=402, mm=7.54, inch=0.30), z=AxisPosition(steps=3, mm=0.0, inch=0.0), bytes=PulsPosition(total=238043, processed=61200))
|
||||
2026-08-17T09:42:34.702311+00:00 gfcloud[1522] INFO machine:_switch_event button pushed
|
||||
2026-08-17T09:42:34.704560+00:00 gfcloud[1522] INFO machine:_run_loop button pressed while paused; resuming (laser lead 1950 ticks)
|
||||
2026-08-17T09:42:34.901002+00:00 gfcloud[1522] INFO machine:_switch_event button released
|
||||
2026-08-17T09:42:58.130807+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:42:58.136283+00:00 gfcloud[1522] INFO machine:_run_loop finished run
|
||||
2026-08-17T09:42:58.137979+00:00 gfcloud[1522] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:42:58.151159+00:00 gfcloud[1522] INFO machine:_motion_locked end positions (actual/expected): X (7573/7573), Y (684/684), Z (3/3)
|
||||
2026-08-17T09:42:58.152103+00:00 gfcloud[1522] INFO machine:_motion_locked motion bytes actual:238043, expected: 238043
|
||||
2026-08-17T09:42:58.160180+00:00 gfcloud[1522] INFO machine:_return_home start return home
|
||||
2026-08-17T09:42:59.047247+00:00 gfcloud[1522] INFO machine:_run_loop starting run
|
||||
2026-08-17T09:42:59.048617+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:42:59.052977+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.RUNNING
|
||||
2026-08-17T09:43:04.170521+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:43:04.176059+00:00 gfcloud[1522] INFO machine:_run_loop finished run
|
||||
2026-08-17T09:43:04.177738+00:00 gfcloud[1522] INFO machine:_return_home return home complete
|
||||
2026-08-17T09:43:04.179696+00:00 gfcloud[1522] INFO machine:_motion_locked start cool down
|
||||
2026-08-17T09:43:04.198973+00:00 gfcloud[1522] INFO machine:_motion_locked start idle
|
||||
2026-08-17T09:43:04.200314+00:00 gfcloud[1522] INFO cnc:set_x_current 33
|
||||
2026-08-17T09:43:04.201774+00:00 gfcloud[1522] INFO cnc:set_y_current 5
|
||||
2026-08-17T09:43:04.210089+00:00 gfcloud[1522] INFO machine:_motion_locked end positions (0, 0, 3)
|
||||
2026-08-17T09:43:04.210885+00:00 gfcloud[1522] INFO machine:_motion end motion
|
||||
2026-08-17T09:43:04.211563+00:00 gfcloud[1522] INFO basemachine:_finish_action print [1576550507]: finished with event ":completed"
|
||||
2026-08-17T09:43:04.213071+00:00 gfcloud[1522] INFO cnc:laser_latch 1
|
||||
2026-08-17T09:43:04.649180+00:00 gfcloud[1522] INFO gfuiservice:run service action request: hunt (ready)
|
||||
2026-08-17T09:43:04.651286+00:00 gfcloud[1522] INFO z_axis:home starting z homing cycle
|
||||
2026-08-17T09:43:04.652782+00:00 gfcloud[1522] INFO gfuiservice:run hunt (ready) service action hunt
|
||||
2026-08-17T09:43:11.298209+00:00 gfcloud[1522] INFO machine:_motion start motion
|
||||
2026-08-17T09:43:11.983820+00:00 gfcloud[1522] INFO machine:_run_loop starting run
|
||||
2026-08-17T09:43:13.630807+00:00 gfcloud[1522] INFO machine:_run_loop current state: MachineState.IDLE
|
||||
2026-08-17T09:43:13.636283+00:00 gfcloud[1522] INFO machine:_run_loop finished run
|
||||
2026-08-17T09:43:13.664853+00:00 gfcloud[1522] INFO machine:_motion end motion
|
||||
2026-08-17T09:43:13.665575+00:00 gfcloud[1522] INFO basemachine:_finish_action hunt [1576550560]: finished with event ":completed"
|
||||
@@ -76,3 +76,89 @@ def make_test(id, covers, always=False, requires=(), kind="auto", fn=None, subsy
|
||||
|
||||
def registry(*tests):
|
||||
return {t.id: t for t in tests}
|
||||
|
||||
|
||||
# ------------------------------------------------------- fake forgectrl
|
||||
|
||||
class FakeForgectrl:
|
||||
"""A stand-in for the machine-services daemon on localhost: canned
|
||||
JSON for the endpoints the suite reads, a mutable `state` the test
|
||||
scripts, every POST recorded, and an optional `on_post(path, form)`
|
||||
hook returning (status, body) to script the daemon's reactions.
|
||||
Point the suite at it with FORGECTRL_URL (see start/stop)."""
|
||||
|
||||
def __init__(self):
|
||||
import http.server
|
||||
import json as _json
|
||||
import threading as _threading
|
||||
import urllib.parse as _up
|
||||
self.state = {
|
||||
"mode": {"mode": "grbl", "controller": "running", "pid": 100, "motion": "verified"},
|
||||
"status": {"state": "idle", "homed": False, "diag": False, "laser_locked": True,
|
||||
"pos": {"x": 0.0, "y": 0.0, "z": 0.0},
|
||||
"switches": {"lid": True, "button": False, "interlock_ok": True,
|
||||
"head": True, "hv_enable": False}},
|
||||
"cool": {"phase": "idle", "armed": False, "hold": False},
|
||||
"cam": {"running": False, "clients": 0},
|
||||
"diag": {"running": False},
|
||||
"settings": {"controller_mode": "grbl", "lid_lamp_idle": ""},
|
||||
}
|
||||
self.posts = []
|
||||
self.on_post = None
|
||||
fake = self
|
||||
|
||||
class H(http.server.BaseHTTPRequestHandler):
|
||||
def log_message(self, *a):
|
||||
pass
|
||||
|
||||
def _send(self, st, body):
|
||||
data = _json.dumps(body).encode()
|
||||
self.send_response(st)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def do_GET(self):
|
||||
path = self.path.split("?", 1)[0]
|
||||
key = {"/mode": "mode", "/status": "status", "/cool/status": "cool", "/cam/status": "cam",
|
||||
"/diag/status": "diag", "/settings": "settings"}.get(path)
|
||||
if key is None:
|
||||
return self._send(404, {"error": "no " + path})
|
||||
self._send(200, fake.state[key])
|
||||
|
||||
def do_POST(self):
|
||||
path, _, query = self.path.partition("?")
|
||||
n = int(self.headers.get("Content-Length") or 0)
|
||||
raw = self.rfile.read(n).decode() if n else ""
|
||||
form = dict(_up.parse_qsl(raw)) if raw else dict(_up.parse_qsl(query))
|
||||
fake.posts.append((path, form))
|
||||
if fake.on_post:
|
||||
r = fake.on_post(path, form)
|
||||
if r is not None:
|
||||
return self._send(*r)
|
||||
if path == "/mode" and form.get("controller"):
|
||||
fake.state["mode"] = dict(fake.state["mode"], mode=form["controller"], controller="running",
|
||||
pid=fake.state["mode"].get("pid", 0) + 1)
|
||||
fake.state["settings"]["controller_mode"] = form["controller"]
|
||||
elif path == "/settings":
|
||||
fake.state["settings"].update(form)
|
||||
self._send(200, {"ok": True})
|
||||
|
||||
self._srv = http.server.ThreadingHTTPServer(("127.0.0.1", 0), H)
|
||||
self._srv.daemon_threads = True
|
||||
self._srv.block_on_close = False # never wait on a lingering connection
|
||||
self.url = "http://127.0.0.1:%d" % self._srv.server_address[1]
|
||||
self._th = _threading.Thread(target=self._srv.serve_forever, daemon=True)
|
||||
|
||||
def start(self):
|
||||
self._th.start()
|
||||
os.environ["FORGECTRL_URL"] = self.url
|
||||
os.environ["FORGECTRL_TOKEN_FILE"] = os.devnull
|
||||
return self
|
||||
|
||||
def stop(self):
|
||||
self._srv.shutdown()
|
||||
self._srv.server_close()
|
||||
os.environ.pop("FORGECTRL_URL", None)
|
||||
os.environ.pop("FORGECTRL_TOKEN_FILE", None)
|
||||
|
||||
@@ -266,3 +266,95 @@ class BaselineTests(unittest.TestCase):
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
class BaselineModeTests(BaselineTests):
|
||||
"""The baseline against a fake forgectrl: what the mode in force
|
||||
owns. Reuses the fake sysfs tree of BaselineTests; only the new
|
||||
tests run here (the inherited ones are skipped)."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
import helpers
|
||||
self.fc = helpers.FakeForgectrl().start()
|
||||
baseline.Baseline._unreachable_until = 0.0
|
||||
|
||||
def tearDown(self):
|
||||
self.fc.stop()
|
||||
super().tearDown()
|
||||
|
||||
def run(self, result=None):
|
||||
# only this class's own tests, not the base class's
|
||||
if self._testMethodName not in BaselineModeTests.__dict__:
|
||||
return
|
||||
return super().run(result)
|
||||
|
||||
def cloud(self):
|
||||
self.fc.state["mode"] = {"mode": "cloud", "controller": "running", "pid": 7, "motion": "verified"}
|
||||
self.fc.state["settings"]["controller_mode"] = "cloud"
|
||||
|
||||
def test_grbl_mode_restores_the_controller_values_and_the_lamp(self):
|
||||
self._attr("cnc/step_freq", "10000")
|
||||
self._attr("pic/lid_led", "77")
|
||||
left = self.bl().enforce("pre", captured=None)
|
||||
self.assertEqual(sorted(x.item for x in left), ["cnc/step_freq", "pic/lid_led"])
|
||||
self.assertEqual(self._read("cnc/step_freq"), "28160")
|
||||
self.assertEqual(self._read("pic/lid_led"), "236")
|
||||
|
||||
def test_cloud_mode_leaves_the_clients_config_lamp_and_counters(self):
|
||||
self.cloud()
|
||||
self._attr("cnc/step_freq", "10000") # the cloud client's tick
|
||||
self._attr("pic/x_step_current", "135")
|
||||
self._attr("pic/lid_led", "77") # its lid-image level
|
||||
b = self.bl()
|
||||
cap = b.capture()
|
||||
self.assertEqual(cap["mode"], "cloud")
|
||||
self.assertNotIn("controller_mode", cap["settings"])
|
||||
self._pos(-13096, -7400, 0) # the service re-zeroed and moved
|
||||
self._attr("cnc/streaming", "1") # NOT the client's: still restored
|
||||
left = b.enforce("post", captured=cap)
|
||||
self.assertEqual([x.item for x in left], ["cnc/streaming"])
|
||||
self.assertEqual(self._read("cnc/step_freq"), "10000")
|
||||
self.assertEqual(self._read("pic/x_step_current"), "135")
|
||||
self.assertEqual(self._read("pic/lid_led"), "77")
|
||||
self.assertEqual(self._read("cnc/streaming"), "0")
|
||||
self.assertEqual(self.fc.posts, []) # no mode switch, no settings write
|
||||
|
||||
def test_cloud_mode_still_relocks_the_latch(self):
|
||||
self.cloud()
|
||||
self._attr("cnc/interlock_circuit", "37") # bit 3 clear: unlocked
|
||||
left = self.bl().enforce("post", captured=None)
|
||||
self.assertEqual([x.item for x in left], ["laser_latch"])
|
||||
self.assertEqual(self._read("cnc/laser_latch"), "1")
|
||||
|
||||
def test_undeclared_mode_change_is_handed_back_through_the_switch(self):
|
||||
b = self.bl()
|
||||
cap = b.capture() # found in grbl
|
||||
self.assertEqual(cap["mode"], "grbl")
|
||||
self.cloud() # the run left it in cloud, silently
|
||||
left = b.enforce("post", captured=cap)
|
||||
items = {x.item: x for x in left}
|
||||
self.assertIn("mode", items)
|
||||
self.assertEqual(items["mode"].action, "restored")
|
||||
self.assertEqual(self.fc.posts, [("/mode", {"controller": "grbl"})])
|
||||
self.assertEqual(self.fc.state["mode"]["mode"], "grbl")
|
||||
|
||||
def test_declared_mode_change_is_kept(self):
|
||||
b = self.bl()
|
||||
cap = b.capture()
|
||||
self.cloud()
|
||||
cap["mode"] = "cloud" # Context.mode_changed("cloud")
|
||||
left = b.enforce("post", captured=cap)
|
||||
self.assertNotIn("mode", [x.item for x in left])
|
||||
self.assertEqual(self.fc.posts, [])
|
||||
self.assertEqual(self.fc.state["mode"]["mode"], "cloud")
|
||||
|
||||
def test_controller_mode_setting_is_never_written_back_bare(self):
|
||||
b = self.bl()
|
||||
cap = b.capture()
|
||||
self.assertNotIn("controller_mode", cap["settings"])
|
||||
self.fc.state["settings"]["controller_mode"] = "cloud" # a switch persisted it
|
||||
self.fc.state["mode"]["mode"] = "cloud"
|
||||
cap["mode"] = "cloud"
|
||||
b.enforce("post", captured=cap)
|
||||
self.assertEqual([p for p, _ in self.fc.posts if p == "/settings"], [])
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
"""The cloud.* job-behavior tests against a fake forgectrl and a replayed
|
||||
gfcloud log: the real test functions run under the real runner Context,
|
||||
the operator prompts answered by a script that also drives the replay
|
||||
(open the lid -> the machine's lid reads open; close it -> the service's
|
||||
re-hunt lines land). The log lines are the machine's own: bench excerpts
|
||||
(the lid-abort, button-wait, and lid-open-hunt runs) and, for the pause,
|
||||
the run loop's lines as its host test emits them on the print skeleton
|
||||
of the lid-abort excerpt.
|
||||
|
||||
What is proven here: the tests find the right lines in real log noise,
|
||||
reuse an existing cloud session and never switch back, restart the client
|
||||
for a fresh hunt, judge the print's own finish line (not another action's),
|
||||
wait the service's deferred moves out, and fail for the right reasons."""
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import helpers
|
||||
from forgetest.runner import Context, Failed, Run
|
||||
from forgetest.suite import cloud
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
FIX = os.path.join(HERE, "fixtures")
|
||||
|
||||
|
||||
def fixture(name):
|
||||
with open(os.path.join(FIX, "gfcloud-%s.log" % name), "rb") as f:
|
||||
return f.read().decode().splitlines()
|
||||
|
||||
|
||||
def cut(lines, marker, count=1):
|
||||
"""(before, after) at the count-th line containing marker (the line
|
||||
itself opens `after`)."""
|
||||
n = 0
|
||||
for i, ln in enumerate(lines):
|
||||
if marker in ln:
|
||||
n += 1
|
||||
if n == count:
|
||||
return lines[:i], lines[i:]
|
||||
raise KeyError(marker)
|
||||
|
||||
|
||||
class Script:
|
||||
"""Answers every prompt with its first option; a hook per prompt
|
||||
substring drives the fake machine and the log replay."""
|
||||
|
||||
def __init__(self, run, hooks=None):
|
||||
self.run = run
|
||||
self.hooks = hooks or {}
|
||||
self.asked = []
|
||||
self.th = threading.Thread(target=self._loop, daemon=True)
|
||||
self.stop = False
|
||||
|
||||
def start(self):
|
||||
self.th.start()
|
||||
return self
|
||||
|
||||
def _loop(self):
|
||||
seen = None
|
||||
while not self.stop:
|
||||
p = self.run.prompt
|
||||
if p and p["id"] != seen:
|
||||
seen = p["id"]
|
||||
self.asked.append(p["question"])
|
||||
for key, fn in self.hooks.items():
|
||||
if key in p["question"]:
|
||||
fn()
|
||||
self.run.answer(p["id"], p["options"][0])
|
||||
time.sleep(0.02)
|
||||
|
||||
|
||||
class CloudSuiteTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.mkdtemp(prefix="forgetest-cloud-")
|
||||
self.log = os.path.join(self.tmp, "gfcloud.log")
|
||||
open(self.log, "wb").close()
|
||||
self.sysfs = os.path.join(self.tmp, "sysfs") + os.sep
|
||||
os.makedirs(self.sysfs + "cnc")
|
||||
self._attr("cnc/interlock_circuit", "45") # latch locked (bit 3)
|
||||
self._pos(0, 0, 3)
|
||||
os.environ["GF_SYSFS_ROOT"] = self.sysfs
|
||||
self.fc = helpers.FakeForgectrl().start()
|
||||
self.saved = (cloud.GFCLOUD_LOG, cloud.QUIET_S, cloud.QUIET_TIMEOUT_S, cloud.HUNT_TIMEOUT_S)
|
||||
cloud.GFCLOUD_LOG = self.log
|
||||
cloud.QUIET_S = 0.4
|
||||
cloud.QUIET_TIMEOUT_S = 3
|
||||
cloud.HUNT_TIMEOUT_S = 8
|
||||
self.script = None
|
||||
|
||||
def tearDown(self):
|
||||
if self.script:
|
||||
self.script.stop = True
|
||||
self.fc.stop()
|
||||
cloud.GFCLOUD_LOG, cloud.QUIET_S, cloud.QUIET_TIMEOUT_S, cloud.HUNT_TIMEOUT_S = self.saved
|
||||
os.environ.pop("GF_SYSFS_ROOT", None)
|
||||
shutil.rmtree(self.tmp, ignore_errors=True)
|
||||
|
||||
# -- fakes -----------------------------------------------------------
|
||||
def _attr(self, attr, val):
|
||||
with open(self.sysfs + attr, "w") as f:
|
||||
f.write(val)
|
||||
|
||||
def _pos(self, x, y, z):
|
||||
with open(self.sysfs + "cnc/position", "wb") as f:
|
||||
f.write(struct.pack("<3i2I", x, y, z, 0, 0))
|
||||
|
||||
def append(self, lines, delay=0.0):
|
||||
def go():
|
||||
if delay:
|
||||
time.sleep(delay)
|
||||
with open(self.log, "ab") as f:
|
||||
f.write(("\n".join(lines) + "\n").encode())
|
||||
if delay:
|
||||
threading.Thread(target=go, daemon=True).start()
|
||||
else:
|
||||
go()
|
||||
|
||||
def in_cloud(self, pid=2278):
|
||||
self.fc.state["mode"] = {"mode": "cloud", "controller": "running", "pid": pid, "motion": "verified"}
|
||||
self.fc.state["settings"]["controller_mode"] = "cloud"
|
||||
|
||||
def lid(self, closed):
|
||||
self.fc.state["status"]["switches"]["lid"] = bool(closed)
|
||||
|
||||
def run_test(self, fn, hooks=None, test_id="cloud.x"):
|
||||
run = Run("test", test_id, test_id)
|
||||
run.baseline_captured = {"mode": self.fc.state["mode"]["mode"], "position": [0, 0, 0]}
|
||||
ctx = Context(run, None, helpers.make_test(test_id, []))
|
||||
self.script = Script(run, hooks).start()
|
||||
try:
|
||||
fn(ctx)
|
||||
finally:
|
||||
self.script.stop = True
|
||||
return run
|
||||
|
||||
def assertFails(self, fn, needle, hooks=None):
|
||||
with self.assertRaises(Failed) as cm:
|
||||
self.run_test(fn, hooks)
|
||||
self.assertIn(needle, str(cm.exception))
|
||||
return cm.exception
|
||||
|
||||
# -- session detection -------------------------------------------------
|
||||
def test_session_live_is_per_client(self):
|
||||
lines = fixture("huntlid")
|
||||
# the old client (1927) closed and left; the new one (2278) is ready
|
||||
self.append(lines)
|
||||
self.assertTrue(cloud.session_live(2278)[0])
|
||||
self.assertFalse(cloud.session_live(1927)[0])
|
||||
# an unknown pid falls back to the newest lines (ready)
|
||||
self.assertTrue(cloud.session_live(9999)[0])
|
||||
# a drop after the ready line: not live until it is ready again
|
||||
self.append(["2026-08-17T10:00:00.000000+00:00 gfcloud[2278] INFO websocket:_on_close RX-EVENT: closed (1006, )",
|
||||
"2026-08-17T10:00:00.100000+00:00 gfcloud[2278] INFO websocket:run RECONNECTING"])
|
||||
live, detail = cloud.session_live(2278)
|
||||
self.assertFalse(live)
|
||||
self.assertIn("RECONNECTING", detail)
|
||||
self.append(["2026-08-17T10:00:06.000000+00:00 gfcloud[2278] INFO websocket:_on_open RX-EVENT: ready"])
|
||||
self.assertTrue(cloud.session_live(2278)[0])
|
||||
|
||||
def test_enter_cloud_reuses_a_live_session_and_never_switches(self):
|
||||
self.in_cloud()
|
||||
self.append(fixture("huntlid"))
|
||||
run = self.run_test(lambda ctx: cloud.enter_cloud(ctx))
|
||||
self.assertTrue(any("reusing it" in l for l in run.lines), run.lines)
|
||||
self.assertEqual(self.fc.posts, [])
|
||||
self.assertEqual(run.baseline_captured["mode"], "cloud")
|
||||
|
||||
def test_enter_cloud_refuses_a_dead_session(self):
|
||||
self.in_cloud(pid=1927) # the client that shut down in the excerpt
|
||||
self.append(fixture("huntlid"))
|
||||
self.assertFails(cloud.enter_cloud, "no live service session")
|
||||
|
||||
def test_enter_cloud_switches_once_from_grbl_and_declares_it(self):
|
||||
lines = fixture("huntlid")
|
||||
pre, post = cut(lines, "gfuiservice:__init__ INITIALIZED")
|
||||
self.append(pre)
|
||||
|
||||
def on_post(path, form):
|
||||
if path == "/mode":
|
||||
self.append(post, delay=0.2) # the new client's session + hunt + moves
|
||||
return None
|
||||
self.fc.on_post = on_post
|
||||
run = self.run_test(lambda ctx: cloud.enter_cloud(ctx))
|
||||
self.assertEqual([p for p, _ in self.fc.posts], ["/mode"])
|
||||
self.assertEqual(self.fc.state["mode"]["mode"], "cloud")
|
||||
self.assertEqual(run.baseline_captured["mode"], "cloud") # declared: the baseline keeps it
|
||||
self.assertTrue(any("connect-time hunt" in l and ":completed" in l for l in run.lines), run.lines)
|
||||
|
||||
def test_enter_cloud_waits_for_the_service_to_stop_moving(self):
|
||||
self.in_cloud()
|
||||
self.append(fixture("huntlid"))
|
||||
# a motion in flight (never idle within the timeout) fails, and says so
|
||||
self.fc.state["status"]["state"] = "running"
|
||||
self.assertFails(cloud.enter_cloud, "still running service moves")
|
||||
|
||||
# -- the hunt with the lid open ------------------------------------------
|
||||
def test_hunt_lid_open_restarts_the_client_in_cloud_mode(self):
|
||||
self.in_cloud(pid=1927)
|
||||
lines = fixture("huntlid")
|
||||
pre, post = cut(lines, "gfuiservice:__init__ INITIALIZED")
|
||||
hunt_part, close_part = cut(post, "_switch_event lid closed")
|
||||
self.append(pre)
|
||||
|
||||
def on_post(path, form):
|
||||
if path == "/controller/stop":
|
||||
self.fc.state["mode"] = dict(self.fc.state["mode"], controller="standby", pid=0)
|
||||
elif path == "/controller/start":
|
||||
self.fc.state["mode"] = dict(self.fc.state["mode"], controller="running", pid=2278)
|
||||
self.append(hunt_part, delay=0.2)
|
||||
return None
|
||||
self.fc.on_post = on_post
|
||||
|
||||
def lid_closed():
|
||||
self.lid(True)
|
||||
self.append(close_part, delay=0.2)
|
||||
run = self.run_test(cloud.hunt_lid_open,
|
||||
hooks={"Open the lid": lambda: self.lid(False), "Close the lid": lid_closed},
|
||||
test_id="cloud.hunt-lid-open")
|
||||
self.assertEqual([p for p, _ in self.fc.posts], ["/controller/stop", "/controller/start"])
|
||||
ev = run.evidence
|
||||
self.assertIn(":completed", ev["hunt_line"])
|
||||
self.assertEqual(ev["refusals_before_hunt_end"], 0)
|
||||
self.assertEqual(ev["motions_after_lid_close"], 3)
|
||||
self.assertEqual(self.fc.state["mode"]["mode"], "cloud") # still cloud
|
||||
self.assertTrue(any("PASS:" in l for l in run.lines))
|
||||
# the prompts, in order: open, confirm lens, close
|
||||
self.assertEqual(len(self.script.asked), 3)
|
||||
|
||||
def test_hunt_lid_open_from_grbl_switches_and_stays(self):
|
||||
lines = fixture("huntlid")
|
||||
pre, post = cut(lines, "gfuiservice:__init__ INITIALIZED")
|
||||
hunt_part, close_part = cut(post, "_switch_event lid closed")
|
||||
self.append(pre)
|
||||
|
||||
def on_post(path, form):
|
||||
if path == "/mode":
|
||||
self.append(hunt_part, delay=0.2)
|
||||
return None
|
||||
self.fc.on_post = on_post
|
||||
run = self.run_test(cloud.hunt_lid_open,
|
||||
hooks={"Open the lid": lambda: self.lid(False),
|
||||
"Close the lid": lambda: (self.lid(True), self.append(close_part, delay=0.2))})
|
||||
self.assertEqual([p for p, _ in self.fc.posts], ["/mode"])
|
||||
self.assertEqual(self.fc.state["mode"]["mode"], "cloud")
|
||||
self.assertEqual(run.baseline_captured["mode"], "cloud")
|
||||
self.assertIn(":completed", run.evidence["hunt_line"])
|
||||
|
||||
def test_hunt_lid_open_needs_the_lid_open(self):
|
||||
self.in_cloud()
|
||||
self.append(fixture("huntlid"))
|
||||
self.assertFails(cloud.hunt_lid_open, "lid reads closed")
|
||||
|
||||
def test_hunt_refused_for_the_lid_fails(self):
|
||||
self.in_cloud(pid=1927)
|
||||
lines = fixture("huntlid")
|
||||
pre, post = cut(lines, "gfuiservice:__init__ INITIALIZED")
|
||||
# a refusal ahead of the hunt's end (the lid gating the hunt would look like this)
|
||||
i = next(i for i, l in enumerate(post) if "z_axis:home starting z homing cycle" in l)
|
||||
post = post[:i] + ["2026-08-17T09:45:10.595000+00:00 gfcloud[2278] INFO machine:_safe_to_move lid opened, unsafe to move"] + post[i:]
|
||||
self.append(pre)
|
||||
|
||||
def on_post(path, form):
|
||||
if path == "/controller/stop":
|
||||
self.fc.state["mode"] = dict(self.fc.state["mode"], controller="standby", pid=0)
|
||||
elif path == "/controller/start":
|
||||
self.fc.state["mode"] = dict(self.fc.state["mode"], controller="running", pid=2278)
|
||||
self.append(post, delay=0.1)
|
||||
return None
|
||||
self.fc.on_post = on_post
|
||||
self.assertFails(cloud.hunt_lid_open, "refused for the lid", hooks={"Open the lid": lambda: self.lid(False)})
|
||||
|
||||
# -- pause / resume ---------------------------------------------------------
|
||||
def replay_print(self, name, at_run, at_end, tail_delay=0.3):
|
||||
"""Split a print excerpt into: up to the run's RUNNING line
|
||||
(lands when the operator says Print is done), the block landing at
|
||||
the mid-run prompt, and the rest."""
|
||||
lines = fixture(name)
|
||||
pre, rest = cut(lines, "waiting for button")
|
||||
run_pre, rest = cut(rest, "current state: MachineState.RUNNING") # the PRINT's run
|
||||
pre, rest = pre + run_pre + [rest[0]], rest[1:]
|
||||
mid, tail = cut(rest, at_end)
|
||||
return {"Click Done here": lambda: self.append(pre, delay=0.1),
|
||||
at_run: lambda: (self.append(mid, delay=0.05), self.append(tail, delay=tail_delay))}
|
||||
|
||||
def test_pause_resume_passes_on_the_machines_lines(self):
|
||||
self.in_cloud(pid=1522)
|
||||
self.append(["2026-08-17T09:41:00.000000+00:00 gfcloud[1522] INFO authentication:authenticate_machine SUCCESS",
|
||||
"2026-08-17T09:41:00.500000+00:00 gfcloud[1522] INFO websocket:_on_open RX-EVENT: ready",
|
||||
"2026-08-17T09:41:00.900000+00:00 gfcloud[1522] INFO websocket:ws_connect ESTABLISHED"])
|
||||
hooks = self.replay_print("pause", "Press the button once NOW", "current state: MachineState.IDLE")
|
||||
self.fc.state["cool"]["armed"] = True
|
||||
run = self.run_test(cloud.pause_resume, hooks=hooks, test_id="cloud.pause-resume")
|
||||
ev = run.evidence
|
||||
self.assertEqual(ev["log"], {"button pressed mid-run; pausing": True, "paused at": True,
|
||||
"button pressed while paused; resuming": True})
|
||||
self.assertTrue(ev["armed_after_resume"])
|
||||
self.assertIn(":completed", ev["log_end"]["print finished"])
|
||||
self.assertTrue(ev["log_end"]["return home complete"])
|
||||
self.assertEqual(ev["relock_or_cancel_lines"], 0)
|
||||
self.assertEqual(self.fc.posts, [])
|
||||
self.assertTrue(any("PASS: button pause/resume" in l for l in run.lines))
|
||||
# the post-print hunt was waited out
|
||||
self.assertTrue(any("machine is quiet" in l for l in run.lines))
|
||||
|
||||
def test_pause_resume_fails_when_the_print_is_cancelled_instead(self):
|
||||
self.in_cloud(pid=1522)
|
||||
self.append(["2026-08-17T09:41:00.500000+00:00 gfcloud[1522] INFO websocket:_on_open RX-EVENT: ready"])
|
||||
lines = fixture("pause")
|
||||
lines = [l.replace('print [1576550507]: finished with event ":completed"',
|
||||
'print [1576550507]: finished with event ":cancelled"') for l in lines]
|
||||
pre, rest = cut(lines, "current state: MachineState.RUNNING")
|
||||
pre, rest = pre + [rest[0]], rest[1:]
|
||||
hooks = {"Click Done here": lambda: self.append(pre, delay=0.1),
|
||||
"Press the button once NOW": lambda: self.append(rest, delay=0.05)}
|
||||
self.assertFails(cloud.pause_resume, "did not complete after the resume", hooks=hooks)
|
||||
|
||||
def test_pause_resume_fails_without_the_pause_line(self):
|
||||
self.in_cloud(pid=1522)
|
||||
self.append(["2026-08-17T09:41:00.500000+00:00 gfcloud[1522] INFO websocket:_on_open RX-EVENT: ready"])
|
||||
lines = [l for l in fixture("pause") if "button pressed mid-run" not in l]
|
||||
pre, rest = cut(lines, "current state: MachineState.RUNNING")
|
||||
pre, rest = pre + [rest[0]], rest[1:]
|
||||
# the wait for the pause lines is 90 s: shorten it through the module's poll by
|
||||
# ending the log early - the finish line arrives, but the pause never does
|
||||
saved = cloud.wait_log
|
||||
|
||||
def fast_wait_log(ctx, offset, needles, timeout, poll=0.5):
|
||||
return saved(ctx, offset, needles, min(timeout, 1.5), poll=0.1)
|
||||
cloud.wait_log = fast_wait_log
|
||||
try:
|
||||
hooks = {"Click Done here": lambda: self.append(pre, delay=0.1),
|
||||
"Press the button once NOW": lambda: self.append(rest, delay=0.05)}
|
||||
self.assertFails(cloud.pause_resume, "did not pause the run", hooks=hooks)
|
||||
finally:
|
||||
cloud.wait_log = saved
|
||||
|
||||
# -- the two lid tests, on their bench excerpts --------------------------------
|
||||
def test_lid_abort_on_the_bench_excerpt(self):
|
||||
self.in_cloud(pid=1522)
|
||||
self.append(["2026-08-17T09:41:00.500000+00:00 gfcloud[1522] INFO websocket:_on_open RX-EVENT: ready"])
|
||||
hooks = self.replay_print("lidabort", "Open the lid NOW", "start cool down")
|
||||
run = self.run_test(cloud.lid_abort, hooks=hooks, test_id="cloud.lid-abort")
|
||||
ev = run.evidence
|
||||
self.assertLess(ev["edge_to_stop_ms"], 60)
|
||||
self.assertIn(":cancelled", ev["log"]["print finished"])
|
||||
self.assertEqual(ev["kernel_counters_after_park"], [0, 0, 3])
|
||||
self.assertTrue(ev["latch_locked"])
|
||||
self.assertFalse(ev["armed_after"])
|
||||
self.assertEqual(self.fc.posts, [])
|
||||
self.assertTrue(any("PASS: lid open" in l for l in run.lines))
|
||||
|
||||
def test_lid_during_button_wait_on_the_bench_excerpt(self):
|
||||
self.in_cloud(pid=1927)
|
||||
self.append(["2026-08-17T09:41:00.500000+00:00 gfcloud[1927] INFO websocket:_on_open RX-EVENT: ready"])
|
||||
lines = fixture("buttonwait")
|
||||
pre, rest = cut(lines, "waiting for button")
|
||||
pre, rest = pre + [rest[0]], rest[1:]
|
||||
hooks = {"Click Done here": lambda: self.append(pre, delay=0.1),
|
||||
"Open the lid now": lambda: self.append(rest, delay=0.05)}
|
||||
run = self.run_test(cloud.lid_during_button_wait, hooks=hooks, test_id="cloud.lid-during-button-wait")
|
||||
ev = run.evidence
|
||||
self.assertEqual(ev["runs_started_after_wait"], 0)
|
||||
self.assertIn(":cancelled", ev["log"]["print finished"])
|
||||
self.assertTrue(ev["latch_locked"])
|
||||
self.assertEqual(self.fc.posts, [])
|
||||
self.assertTrue(any("PASS: lid open at the button prompt" in l for l in run.lines))
|
||||
|
||||
def test_print_finish_is_the_prints_not_another_actions(self):
|
||||
# a motion that completes before the print must not satisfy the print's finish
|
||||
lines = fixture("lidabort")
|
||||
i = cloud.action_finish_index(lines, "print")
|
||||
j = cloud.action_finish_index(lines, "motion")
|
||||
self.assertIsNotNone(i)
|
||||
self.assertIsNotNone(j)
|
||||
self.assertLess(j, i)
|
||||
self.assertIn(":cancelled", lines[i])
|
||||
self.assertIn(":completed", lines[j])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -181,6 +181,18 @@ class ServerTests(unittest.TestCase):
|
||||
st, d = self.call("POST", "/start", {"test": "fake.needs"})
|
||||
self.assertEqual(st, 409)
|
||||
self.assertIn("prerequisites", d["message"])
|
||||
# the operator's override: the test runs alone and the record says so
|
||||
st, d = self.call("POST", "/start", {"test": "fake.needs", "ignore_requires": True})
|
||||
self.assertEqual(st, 200)
|
||||
state = self.wait_idle()
|
||||
self.assertEqual(state["tests"]["fake.needs"]["status"], "pass")
|
||||
st, rec = self.call("GET", "/result?test=fake.needs")
|
||||
self.assertEqual(rec["evidence"]["prerequisites"]["missing"], ["fake.prompt"])
|
||||
self.assertTrue(rec["evidence"]["prerequisites"]["overridden"])
|
||||
self.assertTrue(any("prerequisites overridden" in l for l in rec["log"]))
|
||||
# its prerequisite is still required for the release
|
||||
self.assertFalse(state["tests"]["fake.prompt"]["satisfied"])
|
||||
self.assertFalse(state["authorized"])
|
||||
st, d = self.call("POST", "/start", {"test": "fake.live"})
|
||||
self.assertEqual(st, 409)
|
||||
self.assertIn("live", d["message"])
|
||||
|
||||
Reference in New Issue
Block a user