Skip to content

Automatic pier flip in TelescopeBase (replaces chimera-autopierchange) - #281

Open
wschoenell wants to merge 2 commits into
astroufsc:masterfrom
wschoenell:feat/telescope-pier-flip
Open

Automatic pier flip in TelescopeBase (replaces chimera-autopierchange)#281
wschoenell wants to merge 2 commits into
astroufsc:masterfrom
wschoenell:feat/telescope-pier-flip

Conversation

@wschoenell

Copy link
Copy Markdown
Contributor

Folds the chimera-autopierchange plugin into the telescope itself, the way per-filter focus offsets went into FilterWheelBase in #259. A German equatorial mount that cannot be trusted to track past the meridian is not something a controller should be watching from outside; it is part of what driving that mount means.

What changes

TelescopeBase.control() becomes concrete: it runs the pier flip check on every cycle and then calls the driver's new _control() primitive. Drivers keep their periodic work and stop deciding whether the control loop runs at all. FakeTelescope is migrated here.

telescope:
  name: fake
  type: FakeTelescope
  pier_flip_ha: 0     # hours; 0 flips at the meridian, 0.5 buys 30 more minutes

Leaving pier_flip_ha unset — the default, and the whole story on a fork or alt-az mount — returns before the check asks the mount anything.

The mount is flipped by re-slewing it to the position it is already pointing at, which is what makes it pick the other side of the pier. Only a mount that reached the limit by tracking is flipped: one that slewed straight to an object already past it is left on the side its own driver chose.

The telescope's loop rate drops to one cycle every 5 s, since what the base now runs there asks the mount where it is and a flip is a minutes-scale event. A driver wanting a faster loop still calls set_hz() in __start__, which runs after __init__.

Why not leave it in the plugin

  • It inferred the pier side from the hour angle at the end of every slew, and stored it in TelescopePierSide with the opposite sense of the ASCOM convention that enum otherwise carries (pierEast is the normal pointing state, looking west). It also only saw the slews it was subscribed for, so a restart mid-target left it on UNKNOWN forever. What the check actually needs is one flag: armed by observing the mount track east of the limit, cleared by the flip. A chimera restarted mid-target then waits for the next crossing instead of guessing.
  • Its hour angle was a bare lst - ra, never wrapped. With LST at 0.5 h and a target at RA 23.5 h — one hour past the meridian, i.e. overdue — it computed −23 h and concluded there were 23 hours to go. The flip never came. Fixed at the root in Site.ra_to_ha(), which now returns [-12, +12), in its own commit with its own test.
  • A failed flip was swallowed by a blanket except Exception around the whole loop body. The mount is tracking into the pier at that point, so this is the one failure that must not be quiet: the check now stays armed and retries on the next cycle, and the exception reaches the control loop, which logs it (core: control loop survives exceptions from control() #272).

Two options are gone. dome (the plugin re-synced the dome itself): the flip fires slew_begin/slew_complete like any other slew, so a dome in track mode follows on its own. check_interval: the check runs on the object's existing control loop.

Compatibility

Out-of-tree drivers that still implement control() keep working — they just don't get the flip, exactly as before this PR. TheSkyXTelescope, AlpacaTelescope and the rest that never overrode it get it from config alone. chimera-astelco overrides control() and wants a one-line rename to _control() to get the flip; issue opened there. So does chimera-bisque's legacy TheSkyTelescope, superseded by TheSkyXTelescope.

Users of the plugin drop the AutoPierChange controller block and move ha_flip onto the telescope as pier_flip_ha; documented in docs/configuring.rst.

Testing

8 new tests in tests/chimera/instruments/test_telescope.py cover the crossing, the flip firing exactly once, a slew that lands past the limit not flipping, slewing and not-tracking disarming the check, a non-zero limit, the driver's _control() still running every cycle, and a failed flip being retried. One more in tests/chimera/core/test_site.py pins the hour angle range across the 0/24 h wrap. Full suite: 273 passed, 8 skipped.

Verified end to end against a live chimera with FakeTelescope and the site clock at 60× sidereal, slewing to 18 minutes east of the meridian and letting it track across:

LST is 19.8316 h; slewing to RA 20.1316 h (HA -0.3000 h)
slew done, tracking=True, HA=-0.2158
  t+  0.0s  HA=-0.2158 h  flips=0
  ...
  t+ 13.1s  HA=+0.0028 h  flips=0
  t+ 14.1s  HA=+0.0196 h  flips=1
PIER FLIP: re-slewed to 20.1316 -22.5000 at HA +0.0196
flips after another 5 s: 1
$ chimera-tel --info
telescope: tcp://127.0.0.1:7667/FakeTelescope/fake, device=None
current position ra/dec: 20:07:53.711/-22:30:00.000
current position alt/az: +86:37:04.490/17:59:31.701
tracking: enabled
current side of pier: unknown
automatic pier flip: at hour angle 0 h

and in the server log:

INFO chimera.faketelescope telescope.py:78 Hour angle 0.009 h is past 0 h, flipping the pier.

lst - ra is not an hour angle until it is wrapped: with LST at 0.5 h and
an object at RA 23.5 h, ra_to_ha() answered -23 h for an object one hour
past the meridian. Anything comparing the result against a limit -- the
pier flip below, dome geometry -- reads that as "still 23 hours to go"
and never fires.

Wrap it into [-12, +12), the range the rest of the code already assumes:
east of the meridian negative, west positive.
Folds the chimera-autopierchange plugin into the telescope itself, the
way per-filter focus offsets went into FilterWheelBase (astroufsc#259):
behaviour that only the instrument can get right belongs on the
instrument, not in a controller watching it from outside.

TelescopeBase.control() becomes concrete: it runs the pier flip check on
every cycle and then calls the driver's new _control() primitive, so
drivers keep their periodic work and no longer decide whether the loop
runs at all. FakeTelescope is migrated here. The loop rate drops to one
cycle every 5 s, since what the base now runs there asks the mount where
it is; a driver wanting a faster loop calls set_hz() in __start__ as
before.

A German equatorial mount is flipped by re-slewing it to the position it
is already pointing at, which is what makes the mount pick the other side
of the pier:

    telescope:
      name: fake
      type: FakeTelescope
      pier_flip_ha: 0

Leaving pier_flip_ha unset -- the default, and the whole story on a fork
or alt-az mount -- disables the check before it queries the mount at all.

Only a mount that reached the limit *by tracking* is flipped; one that
slewed straight to an object already past it is left on the side its own
driver chose. The plugin tracked that with a TelescopePierSide it
inferred from the hour angle at the end of every slew, using the opposite
sense of the ASCOM convention the enum otherwise carries, and only for
slews it happened to see. The state here is one flag, armed by observing
the mount track east of the limit, so a chimera restarted mid-target
simply waits for the next crossing instead of guessing.

The plugin also computed the hour angle from a bare lst - ra, so a target
either side of the 0/24 h wrap was 23 hours from a flip that then never
came; see the previous commit.

A flip that raises leaves the check armed and is retried on the next
cycle -- the mount is tracking into the pier, so quietly giving up is the
one thing this must not do. The re-slew fires slew_begin/slew_complete
like any other slew, so a dome in track mode follows along.
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.59259% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.68%. Comparing base (22eaf33) to head (2b6965e).

Files with missing lines Patch % Lines
src/chimera/cli/tel.py 0.00% 1 Missing ⚠️
src/chimera/instruments/telescope.py 95.65% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #281      +/-   ##
==========================================
+ Coverage   51.55%   51.68%   +0.13%     
==========================================
  Files         102      102              
  Lines        8517     8542      +25     
  Branches      978      982       +4     
==========================================
+ Hits         4391     4415      +24     
- Misses       3928     3929       +1     
  Partials      198      198              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread src/chimera/core/site.py
Coord.from_h(ra), Coord.from_r(self.lst_in_rads())
).to_h()
)
return (ha + 12) % 24 - 12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move the coord convention adjustment to CoordUtil.ra_to_ha itself.

self._park_position = None
# True once the mount has been seen tracking east of pier_flip_ha, i.e.
# heading for the flip. See _check_pier_flip().
self._pier_flip_armed = False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This boolean is not needed, you can just take the desired flip/not-flip and return inside _check_pier_flip. A decision doesn't carry for the next cycle, it is always evaluated again.

return

self.log.info(f"Hour angle {ha:.3f} h is past {flip_ha} h, flipping the pier.")
self.slew_to_ra_dec(*self.get_position_ra_dec())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double check if the epoch return by get_position_ra_dec would be the expected one for the next slew so we don't move targets by mistake.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants