Skip to content

Repository files navigation

SharpCap Focus Sequencer

On-demand thermal focus compensator for the ZWO EAF focuser via ASCOM. Reads the regression model and last autofocus reference produced by sharpcap-focus-temperature and moves the focuser to the thermally corrected position.

Designed to be called from a nightly sequencer (SharpCap Advanced Sequencer, NINA, SGP'Pro, etc.) on a periodic schedule.

Thermal compensation formula

focus_target = focus_ref + TCF × (T_current − T_ref)
Variable Description
focus_ref Focuser position at the reference autofocus point
T_ref Temperature at the reference autofocus point
T_current Current temperature read from the EAF external sensor
TCF Temperature compensation factor (steps/°C) = 1/k from the regression

How it works

  1. Refreshes the state JSON automatically by calling sharpcap_focuser.py (sibling repository) — so the latest SharpCap autofocus result is always reflected before each correction.
  2. Connects to the ZWO EAF via ASCOM and reads the real focuser position and temperature.
  3. Aborts immediately if IsMoving = True (e.g. SharpCap autofocus is running) — the next scheduled cycle will retry.
  4. Calculates the thermally compensated target position.
  5. Skips the move if the correction is below --min-correction and a backlash overshoot would be needed; always moves if the target is in the favourable direction (no overshoot required).
  6. Moves the focuser to that position using backlash-compensated movement (always arrives from below).
  7. Updates last_temp_applied and last_focus_applied in the JSON state file.

Two-repository workflow

This project is designed to work alongside sharpcap-focus-temperature as two sibling repositories cloned under the same parent folder. The exact parent path does not matter; only the sibling relationship is required:

<any-parent>\
├── sharpcap-focus-temperature\   ← produces both state JSON files
│   ├── sharpcap_focuser.py
│   ├── sharpcap_focus_state.json        ← main tube  (single source of truth)
│   └── sharpcap_focus_state_guide.json  ← guide tube (single source of truth)
└── sharpcap-focus-sequencer\      ← consumes both state JSON files
    ├── focus_sequencer.py
    ├── run_focus.bat              ← main tube  (C8, via Device Hub)
    └── run_focus_guide.bat        ← guide tube (50ED, direct ASCOM)

Do not copy the state JSON files into this repository. They are generated by sharpcap_focuser.py and must always be read from their original location. Copying them would create stale duplicates that silently drift from the real models.

Nightly imaging loop

[New night]
  │
  ├─ Focus guide tube once at session start:
  │       run_focus_guide.bat        (direct ASCOM.EAF_2.Focuser)
  │
  └─ Main tube imaging sequence loop:
         ├─ Capture subframes
         ├─ Dither
         └─ Run run_focus.bat (calls focus_sequencer.py)
              │
              ├─ busy check (abort if SharpCap autofocus is running)
              ├─ call sharpcap_focuser.py  ← refreshes main tube state JSON
              └─ apply thermal correction to main tube
                   │
                   (repeat every 7 min)
                   │
                   └─ SharpCap PERIODIC Refocus at ΔT = 1 °C
                        └─ next cycle picks up the new reference automatically

Installation

Clone both repositories as sibling directories under the same parent folder:

cd <any-parent>
git clone https://github.com/davidglt/sharpcap-focus-sequencer.git
git clone https://github.com/davidglt/sharpcap-focus-temperature.git

Create and populate each virtual environment:

cd sharpcap-focus-sequencer
python -m venv .venv
.venv\Scripts\pip install -r requirements\requirements.txt

cd ..
cd sharpcap-focus-temperature
python -m venv .venv
.venv\Scripts\pip install -r requirements\requirements.txt

The provided wrapper scripts use %~dp0 to locate themselves, so they work from any parent directory without any path configuration.

Windows Execution Policy

By default, Windows may block scripts downloaded from the internet. To allow the virtual environment activation scripts and .bat wrappers to run, set the execution policy for the current user once from an elevated PowerShell prompt:

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

What this does: allows locally created scripts to run, and allows downloaded scripts that are signed by a trusted publisher. It does not disable Windows Defender or any other security mechanism.

If you prefer a narrower change, you can unblock only the specific files instead:

Unblock-File -Path C:\astro\sharpcap-focus-sequencer\run_focus.bat
Unblock-File -Path C:\astro\sharpcap-focus-sequencer\run_focus_guide.bat
Unblock-File -Path C:\astro\sharpcap-focus-temperature\sharpcap_focuser.py

State JSON location

Both state JSON files live in the sibling repository sharpcap-focus-temperature. The sequencer always reads from and writes to:

Tube State JSON Entry point
Main (C8 + ASI2600MC Pro) ..\sharpcap-focus-temperature\sharpcap_focus_state.json run_focus.bat
Guide (50ED + ASI224MC) ..\sharpcap-focus-temperature\sharpcap_focus_state_guide.json run_focus_guide.bat

Use --state-json <path> only in exceptional cases (e.g. a non-standard clone layout). Do not copy the files into this repository.

Requirements

  • Python 3.10 or newer.
  • Windows (ASCOM platform required).
  • ASCOM Platform installed.
  • ZWO EAF ASCOM driver installed.
  • pywin32
pip install pywin32

Usage

Normal run via wrappers (recommended):

run_focus.bat              # main tube  — C8, via Device Hub
run_focus_guide.bat        # guide tube — 50ED, direct ASCOM

Or directly via Python:

python focus_sequencer.py                                        # main tube (default)
python focus_sequencer.py --ascom-id "ASCOM.EAF_2.Focuser" \    # guide tube (direct)
    --state-json "..\sharpcap-focus-temperature\sharpcap_focus_state_guide.json"

Dry run (connects to the driver, reads real position and temperature, calculates the target, but does not move the focuser and does not update the state JSON):

run_focus.bat --dry-run
run_focus_guide.bat --dry-run

Dry run with temperature override:

run_focus.bat --dry-run --temp 18.5

Custom backlash value (or disable entirely):

run_focus.bat --backlash 300
run_focus.bat --backlash 0   # disable backlash compensation

Custom minimum correction threshold for backlash direction:

run_focus.bat --min-correction 50   # default
run_focus.bat --min-correction 0    # always move in both directions

Command-line options

Option Default Description
--state-json auto-detected Path to the JSON state file produced by sharpcap_focuser.py. If omitted, uses the canonical sibling-repository path.
--ascom-id ASCOM.DeviceHub.Focuser ASCOM ProgID of the focuser driver.
--dry-run off Connect to the driver, read real position and temperature, calculate the target, but do not move the focuser, do not refresh the state JSON, and do not update last_temp_applied. Use --temp to override the sensor reading.
--temp (from sensor) Override the temperature read from the EAF sensor (°C). Requires an ASCOM connection to read the real focuser position. Useful with --dry-run to simulate a specific temperature scenario.
--backlash 500 Backlash compensation in steps. The focuser always arrives at the target from below; if the target is below the current position, it first overshoots to (target − backlash) then moves up. Set to 0 to disable.
--min-correction 50 Minimum correction (steps) required to trigger a move only when a backlash overshoot is needed (target < current position). Moves in the favourable direction (target ≥ current, no overshoot) are always applied regardless of size. With TCF = −61.59 steps/°C, 50 steps ≈ 0.81 °C. Set to 0 to always move in both directions.
--move-timeout 60 Seconds to wait for each focuser move to complete.

Backlash configuration

The ZWO EAF ASCOM driver reports the commanded position, not the physical encoder position. This means the driver's built-in backlash compensation and any optical measurement (double V-curve) are the only reliable methods.

To avoid double-compensation, use only one layer:

Layer Recommended setting Notes
ZWO EAF ASCOM driver 0 Disable — let the script handle it
SharpCap backlash 0 Disable — only affects SharpCap autofocus moves
--backlash (this script) 500 (default, adjust after measurement) Script overshoots then approaches from below

Measuring backlash: Use the optical double V-curve method in SharpCap (run autofocus twice approaching from opposite directions and compare the best-focus positions). The ZWO EAF ASCOM driver reports commanded position only, so software step-counting tools yield 0 and are not useful.

Current configuration: ASCOM driver backlash = 0 (both EAFs), SharpCap backlash = 0. The script uses --backlash 500 by default. Measure the real backlash of your setup and update this value. Typical values for a well-adjusted EAF on a C8 are 100–300 steps.

Busy detection

The script checks IsMoving immediately after connecting. If the focuser is already moving (e.g. SharpCap is running an autofocus), the script logs a warning and exits cleanly without touching the focuser:

2026-08-25 23:21:00 | INFO  | START | ...
2026-08-25 23:21:01 | WARN  | SKIP  | Focuser busy (IsMoving=True) — skipping this cycle, retry in 7 min
2026-08-25 23:21:01 | INFO  | END   | pos=24911 | reason=busy

The next scheduled execution (7 minutes later by default in the SharpCap sequencer) will retry normally.

Minimum correction threshold

Small thermal corrections in the unfavourable direction (focuser must move inward, requiring a backlash overshoot) are skipped when the correction is smaller than --min-correction. This avoids unnecessary double moves (overshoot + return) for insignificant corrections.

Corrections in the favourable direction (focuser moves outward — the normal direction during a cooling night) are always applied, regardless of size. This keeps the focus continuously well-corrected with small, frequent adjustments instead of accumulating drift.

--min-correction Thermal equivalent (TCF = −61.59 steps/°C) When to use
20 ~0.32 °C Very sensitive, moves almost always
50 (default) ~0.81 °C Balanced — recommended
100 ~1.62 °C Conservative, only large corrections

Multiple EAF units

If you have more than one ZWO EAF connected (e.g. main tube + guide tube), the ZWO ASCOM driver registers each unit under a different ProgID:

ProgID Tube Firmware Physical travel Focus position Driver backlash
ASCOM.EAF.Focuser First EAF — main tube (C8 + ASI2600MC Pro) 3.3.8A 0 – 64 264 steps ~25 000 steps 0
ASCOM.EAF_2.Focuser Second EAF — guide tube (50ED + ASI224MC) 3.3.8C 0 – 520 000 steps ~335 000 steps 0

Note on ProgID assignment: ASCOM ProgIDs are assigned by the ZWO driver in USB enumeration order and can change when USB hubs or adapters are added or removed (e.g. adding a CH341T USB-serial adapter). Always verify with detect_focusers.py after any USB topology change.

Note: The guide tube EAF (50ED) must have Max Steps set to 520 000 in ASICap → Focuser → Advanced. After a firmware update or power cycle that resets the counter, use Set Current Position (without moving the focuser) to restore the correct step count before any automated run.

ASCOM access per tube

Tube ProgID Access mode Reason
Main (C8 + ASI2600MC Pro) ASCOM.DeviceHub.Focuser Via ASCOM Device Hub SharpCap and this script both need simultaneous access. Configure Device Hub to proxy ASCOM.EAF.Focuser.
Guide (50ED + ASI224MC) ASCOM.EAF_2.Focuser Direct SharpCap does not access the guide tube EAF. Device Hub is not needed.

Identifying ProgIDs with detect_focusers.py

With both EAF units connected, run:

python detect_focusers.py

This probes ASCOM.EAF.Focuser through ASCOM.EAF_5.Focuser and prints the Name, Position, and Temperature of each unit that responds:

Probing ASCOM focuser ProgIDs...

[OK] ASCOM.EAF.Focuser
     Name        : ZWO Focuser
     Description : ZWO Focuser (1)
     Position    : 25,018 steps    <-- main tube
     Temperature : 27.16 °C

[OK] ASCOM.EAF_2.Focuser
     Name        : ZWO Focuser
     Description : ZWO Focuser (2)
     Position    : 335,675 steps   <-- guide tube
     Temperature : 26.60 °C

Identifying ProgIDs with the ASCOM Chooser (alternative)

If detect_focusers.py does not find your device, use the ASCOM Chooser directly to discover the exact ProgID registered on your system:

import win32com.client

chooser = win32com.client.Dispatch("ASCOM.Utilities.Chooser")
chooser.DeviceType = "Focuser"
prog_id = chooser.Choose("")

print(f"ProgID: '{prog_id}'")

if prog_id:
    f = win32com.client.Dispatch(prog_id)
    f.Connected = True
    print(f"Name       : {f.Name}")
    print(f"Position   : {f.Position}")
    print(f"Temperature: {f.Temperature}")
    f.Connected = False

This opens the ASCOM Chooser window — select the desired EAF and click OK (not the X button). The ProgID, current position, and temperature will be printed to the console. Repeat for each EAF to map all connected units.

Typical workflow

  1. Run detect_focusers.py once (with both EAFs connected) to identify the correct ProgIDs.
  2. Guide tube: at the start of each night, run run_focus_guide.bat once to apply the thermally compensated starting position for the 50ED.
  3. Main tube: in your nightly sequencer (SharpCap Advanced Sequencer, NINA, SGP'Pro), add a Script step after each dither block pointing to run_focus.bat. The wrapper uses %~dp0 so it works from any installation path.
  4. The script auto-detects the state JSON, calls sharpcap_focuser.py to refresh it, reads the real EAF position and temperature, calculates the correction, and moves the focuser.
  5. SharpCap's PERIODIC Refocus WHEN TEMP CHANGES BY 1 triggers a full autofocus when needed — the next cycle of focus_sequencer.py picks up the new reference automatically.

State JSON

This script reads and updates the state JSON files in-place in ..\sharpcap-focus-temperature\.

Main tube example:

{
  "timestamp_ref": "2026-08-24 23:11:32",
  "temp_ref": 18.4,
  "focus_ref": 25342,
  "last_temp_applied": 17.1,
  "last_focus_applied": 25422,
  "model_tcf": -61.59,
  "model_inv_tcf": -0.016237,
  "model_intercept_c": 432.939
}

After each run, last_temp_applied and last_focus_applied are updated to reflect the correction just applied, while focus_ref and temp_ref remain unchanged as the original reference point.

Regenerating the state JSON manually

For diagnostics or a forced refresh without triggering a thermal correction, call sharpcap_focuser.py directly using the sibling repository's own Python:

# Main tube
..\sharpcap-focus-temperature\.venv\Scripts\python.exe ..\sharpcap-focus-temperature\sharpcap_focuser.py

# Guide tube
..\sharpcap-focus-temperature\.venv\Scripts\python.exe ..\sharpcap-focus-temperature\sharpcap_focuser.py --tube guide

Related

License

This project is licensed under the GNU General Public License v3.0 or later.

See the LICENSE.txt file for the full license text.

Author

David González López-Tercero
Website: https://dragonit.es
Email: davidglt@dragonit.es

About

Nightly focus temperature compensation sequencer. Reads the thermal model from sharpcap-focus-temperature and applies automatic focuser corrections via ZWO EAF.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages