From d74a5c6af5818d83042a79ba55119b2bb869d5b3 Mon Sep 17 00:00:00 2001 From: Lutz Date: Tue, 10 Mar 2026 01:09:53 -0700 Subject: [PATCH] V6.6.8.0 ASCOM Driver Updates Used Claude Code to analyze the driver and it found and fixed numerous issues: - Cached profile to reduce disk reads - Removed an access mutex that was superfluous since we were already using lock {} at a lower level - Fixed the :SC# command to return "1" - Fixed sending the :Q# command (did not have trailing #, which probably caused the spurious NINA exception) - Cached the NovaS31 COM object from ASCOM, instead of instantiating and tearing it down all the time. - Protected the version against multi-threaded race condition. - Saved the tracking state per axis (probably not needed, but safer). - Optimized SideOfPier calculation to make far fewer calls to the mount and fixed a numerical wrap issue. - Made the drivers UTCDate return the UTC date from the mount. - Fixed the PollUntilZero function to have a timeout of 60s instead of waiting for all eternity in case of protocol issue. Also made it poll every 500ms instead of 1000ms - Split the exception handling for connection vs. other issue in the Setup dialog - Fixed a few Index-Out-of-range bugs in the Setup Dialog. - Added checks to :GX# processing to make sure we got a full answer. --- .../Properties/AssemblyInfo.cs | 8 +- ASCOM.Driver/OpenAstroTracker/README.txt | 47 +++-- .../OpenAstroTracker/SharedResources.cs | 59 +++--- ASCOM.Driver/TelescopeDriver/Driver.cs | 186 ++++++++++-------- ASCOM.Driver/TelescopeDriver/FocuserDriver.cs | 20 +- .../Properties/AssemblyInfo.cs | 10 +- .../TelescopeDriver/SetupDialogForm.cs | 57 +++++- 7 files changed, 245 insertions(+), 142 deletions(-) diff --git a/ASCOM.Driver/OpenAstroTracker/Properties/AssemblyInfo.cs b/ASCOM.Driver/OpenAstroTracker/Properties/AssemblyInfo.cs index fb499ff..29a6671 100644 --- a/ASCOM.Driver/OpenAstroTracker/Properties/AssemblyInfo.cs +++ b/ASCOM.Driver/OpenAstroTracker/Properties/AssemblyInfo.cs @@ -6,11 +6,11 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("ASCOM OpenAstroTracker")] -[assembly: AssemblyDescription("ASCOM multi-interface server for OpenAstroTracker")] +[assembly: AssemblyDescription("ASCOM multi-interface server for OpenAstroTech Mounts")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("OpenAstroTech")] [assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("Copyright © 2020-2025, OpenAstroTech")] +[assembly: AssemblyCopyright("Copyright © 2020-2026, OpenAstroTech")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -21,7 +21,7 @@ // Build Number // Revision // -[assembly: AssemblyVersion("6.6.7.3")] -[assembly: AssemblyFileVersion("6.6.7.3")] +[assembly: AssemblyVersion("6.6.8.0")] +[assembly: AssemblyFileVersion("6.6.8.0")] [assembly: ComVisibleAttribute(false)] diff --git a/ASCOM.Driver/OpenAstroTracker/README.txt b/ASCOM.Driver/OpenAstroTracker/README.txt index 73d99aa..6bd461b 100644 --- a/ASCOM.Driver/OpenAstroTracker/README.txt +++ b/ASCOM.Driver/OpenAstroTracker/README.txt @@ -1,25 +1,23 @@ +--------------------------------------------------------------------------+ | | -| OpenAstroTracker ASCOM Driver V6.6.7.3 Pre-Release | -| Published: 23. June 2025 | +| OpenAstroTracker ASCOM Driver V6.6.8.0 Pre-Release | +| Published: 11. March 2026 | | | +--------------------------------------------------------------------------+ -This is the latest ASCOM driver available for the OAT and OAM. It allows +This is the latest ASCOM driver available for the OAT, OAM and OAE. It allows various client programs to communicate with the OAT using both standard -LX200 Meade protocol commands, as well as proprietary OAT extensions to -that protocol. +LX200 Meade protocol commands, as well as proprietary OpenAstroTech extensions +to that protocol. -The driver is compatible with ASCOM 6.5 and 7.0. +The driver is compatible with ASCOM 6.5 and 7.x. -The driver also contains controls for the OAT and OAM so that it can be +The driver also contains controls for OpenAstroTech mounts so that they can be initialized, configured, slewed, unparked, homed, focused and parked from the driver Properties dialog. * Tested on MKS Gen L V2.1, Arduino Mega and ESP32. No other variants of Arduino have been tested, or are officially supported. - * Tested with V1.10.4 firmware. - * Tested with V1.11.0 firmware. * Tested with V1.13.12 firmware (RECOMMENDED). * It will probably work with earlier version (down to V1.6.32 and later). @@ -27,12 +25,14 @@ from the driver Properties dialog. Support ------- * For any issues or questions arising from the use of this driver, or any - other OAT-related questions or to interact with the OAT community, - please visit our Discord server. + other OpenAstroTech mount-related questions or to interact with the + OpenAstroTech community, please visit our Discord server. Testing ------- + * 6.6.8.0 Not tested. + * 6.6.7.3 Not tested. * 6.6.7.2 Conformance Test (2025-Apr-20) @@ -75,8 +75,31 @@ So... keep your towel handy and supervise operations. Release History --------------- + - 6.6.8.0 : Pre-Released 2026-03-10 + Cached profile to reduce disk reads + Removed an access mutex that was superfluous since we were already using + lock {} at a lower level + Fixed the :SC# command to return "1" + Fixed sending the :Q# command (did not have trailing #, which probably + caused the spurious NINA exception) + Cached the NovaS31 COM object from ASCOM, instead of instantiating and + tearing it down all the time. + Protected the version against multi-threaded race condition. + Saved the tracking state per axis (probably not needed, but safer). + Optimized SideOfPier calculation to make far fewer calls to the mount + and fixed a numerical wrap issue. + Made the drivers UTCDate return the UTC date from the mount. + Fixed the PollUntilZero function to have a timeout of 60s instead of + waiting for all eternity in case of protocol issue. Also made it poll + every 500ms instead of 1000ms. + Split the exception handling for connection vs. other issue in the + Setup dialog + Fixed a few Index-Out-of-range bugs in the Setup Dialog. + Added checks to :GX# processing to make sure we got a full answer. + - 6.6.7.3 : Pre-Released 2025-06-23 - Added support for ASCOM driver to get and set time and date from and to mount. + Added support for ASCOM driver to get and set time and date from and + to mount. - 6.6.7.2 : Pre-Released 2025-04-20 FindHome and AtHome implemented. diff --git a/ASCOM.Driver/OpenAstroTracker/SharedResources.cs b/ASCOM.Driver/OpenAstroTracker/SharedResources.cs index a09c77b..0ac4e64 100644 --- a/ASCOM.Driver/OpenAstroTracker/SharedResources.cs +++ b/ASCOM.Driver/OpenAstroTracker/SharedResources.cs @@ -1,4 +1,4 @@ -// +// // ================ // Shared Resources // ================ @@ -66,27 +66,20 @@ public enum LoggingFlags private static double latitudeDefault = 30; private static double longitudeDefault = -97; private static double elevationDefault = 1; - private static Mutex _commandMutex; + private static readonly Mutex _commandMutex; + private static ProfileData _cachedProfile; + private static readonly object _profileLock = new object(); static SharedResources() { + _commandMutex = new Mutex(false, "CommMutex"); EnsureLogger(); } // // Public access to shared resources // - public static Mutex OATCommandMutex - { - get - { - if (_commandMutex == null) - { - _commandMutex = new Mutex(false, "CommMutex"); - } - return _commandMutex; - } - } + public static Mutex OATCommandMutex => _commandMutex; private static void EnsureLogger() { @@ -116,19 +109,26 @@ private static void EnsureLogger() public static ProfileData ReadProfile() { - using (Profile driverProfile = new Profile()) + lock (_profileLock) { - driverProfile.DeviceType = "Telescope"; - return new ProfileData + if (_cachedProfile != null) + return _cachedProfile; + + using (Profile driverProfile = new Profile()) { - TraceState = Convert.ToBoolean(driverProfile.GetValue(driverID, traceStateProfileName, String.Empty, traceStateDefault)), - TraceFlags = (LoggingFlags)Enum.Parse(typeof(LoggingFlags), driverProfile.GetValue(driverID, traceFlagsProfileName, String.Empty, traceFlagsDefault)), - ComPort = driverProfile.GetValue(driverID, comPortProfileName, string.Empty, comPortDefault), - BaudRate = long.Parse(driverProfile.GetValue(driverID, baudRateProfileName, string.Empty, baudRateDefault)), - Latitude = Convert.ToDouble(driverProfile.GetValue(driverID, latitudeProfileName, string.Empty, latitudeDefault.ToString())), - Longitude = Convert.ToDouble(driverProfile.GetValue(driverID, longitudeProfileName, string.Empty, longitudeDefault.ToString())), - Elevation = Convert.ToDouble(driverProfile.GetValue(driverID, elevationProfileName, string.Empty, elevationDefault.ToString())) - }; + driverProfile.DeviceType = "Telescope"; + _cachedProfile = new ProfileData + { + TraceState = Convert.ToBoolean(driverProfile.GetValue(driverID, traceStateProfileName, String.Empty, traceStateDefault)), + TraceFlags = (LoggingFlags)Enum.Parse(typeof(LoggingFlags), driverProfile.GetValue(driverID, traceFlagsProfileName, String.Empty, traceFlagsDefault)), + ComPort = driverProfile.GetValue(driverID, comPortProfileName, string.Empty, comPortDefault), + BaudRate = long.Parse(driverProfile.GetValue(driverID, baudRateProfileName, string.Empty, baudRateDefault)), + Latitude = Convert.ToDouble(driverProfile.GetValue(driverID, latitudeProfileName, string.Empty, latitudeDefault.ToString())), + Longitude = Convert.ToDouble(driverProfile.GetValue(driverID, longitudeProfileName, string.Empty, longitudeDefault.ToString())), + Elevation = Convert.ToDouble(driverProfile.GetValue(driverID, elevationProfileName, string.Empty, elevationDefault.ToString())) + }; + return _cachedProfile; + } } } @@ -146,6 +146,11 @@ public static void WriteProfile(ProfileData profile) driverProfile.WriteValue(driverID, elevationProfileName, profile.Elevation.ToString()); currentLogFlags = profile.TraceFlags; } + // Update cache after successful write so subsequent ReadProfile calls skip the registry + lock (_profileLock) + { + _cachedProfile = profile; + } } #region single serial port connector @@ -241,7 +246,9 @@ public static string SendMessage(string message) case ExpectedAnswer.DoubleHashTerminated: retVal = SharedSerial.ReceiveTerminated("#"); retVal = retVal.TrimEnd('#'); - retVal = SharedSerial.ReceiveTerminated("#"); + // Protocol returns two #-terminated strings (e.g. :SC returns "1#Updating Planetary Data#"). + // Return the first string (success indicator); read and discard the trailing info string. + SharedSerial.ReceiveTerminated("#"); break; } @@ -349,7 +356,7 @@ public static bool Connected } } - get => SharedSerial.Connected; + get { lock (lockObject) { return SharedSerial.Connected; } } } diff --git a/ASCOM.Driver/TelescopeDriver/Driver.cs b/ASCOM.Driver/TelescopeDriver/Driver.cs index bd7e547..64fb223 100644 --- a/ASCOM.Driver/TelescopeDriver/Driver.cs +++ b/ASCOM.Driver/TelescopeDriver/Driver.cs @@ -36,6 +36,7 @@ public class Telescope : ReferenceCountedObjectBase, ITelescopeV3 private Action logMessageFunc; private Transform _transform = new Transform(); private Transform _azAltTransform = new Transform(); + private ASCOM.Astrometry.NOVAS.NOVAS31 _novas31; private bool _isParked; private bool _isTracking = true; @@ -45,6 +46,7 @@ public class Telescope : ReferenceCountedObjectBase, ITelescopeV3 private bool _targetDecSet; private bool _isConnected = false; private long _fwVersion = 0; + private readonly object _fwVersionLock = new object(); private ProfileData Profile => SharedResources.ReadProfile(); @@ -59,7 +61,9 @@ public Telescope() SharedResources.SetTraceFlags(Profile.TraceFlags); LogMessage(LoggingFlags.Scope, $"Starting initialization - v{Version}"); - // TODO: Implement your additional construction here + _novas31 = new ASCOM.Astrometry.NOVAS.NOVAS31(); + + // TODO: Implement your additional construction here _transform.SetJ2000(_utilities.HMSToHours("02:31:51.12"), _utilities.DMSToDegrees("89:15:51.4")); _transform.SiteElevation = SiteElevation; _transform.SiteLatitude = SiteLatitude; @@ -147,12 +151,10 @@ public string Action(string ActionName, string ActionParameters) } /// - /// Required Interface functions of ITelescopeV3 + /// Required Interface functions of ITelescopeV3 /// public void CommandBlind(string Command, bool Raw = false) { - SharedResources.OATCommandMutex.WaitOne(); - try { SharedResources.SendMessage(Command); @@ -161,14 +163,10 @@ public void CommandBlind(string Command, bool Raw = false) { LogMessage(LoggingFlags.Scope, "CommandBlind - /// Required Interface functions of ITelescopeV3 + /// Required Interface functions of ITelescopeV3 /// public bool CommandBool(string Command, bool Raw = false) { @@ -176,12 +174,10 @@ public bool CommandBool(string Command, bool Raw = false) } /// - /// Required Interface functions of ITelescopeV3 + /// Required Interface functions of ITelescopeV3 /// public string CommandString(string Command, bool raw = false) { - SharedResources.OATCommandMutex.WaitOne(); - try { var response = SharedResources.SendMessage(Command); @@ -192,10 +188,6 @@ public string CommandString(string Command, bool raw = false) LogMessage(LoggingFlags.Scope, "CommandString " + atHome); return atHome; } @@ -648,7 +647,10 @@ public bool IsPulseGuiding } - private bool _trackingPriorToMove; + // Per-axis saved tracking state, so simultaneous moves on both axes don't overwrite each other. + private bool _trackingPriorToMovePrimary; + private bool _trackingPriorToMoveSecondary; + public void MoveAxis(TelescopeAxes Axis, double Rate) { LogMessage(LoggingFlags.Scope, $"MoveAxis({Axis}, {Rate:0.00})"); @@ -674,22 +676,25 @@ public void MoveAxis(TelescopeAxes Axis, double Rate) var sAxis = Enum.GetName(typeof(TelescopeAxes), Axis); string cmd = "Q"; - if (Rate == 0) { LogMessage(LoggingFlags.Scope, $"MoveAxis - {sAxis} Rate is zero, so stopping Slew and setting Rate S"); - // if at some point we support multiple tracking rates this should set - // the value back to the previous rate... CommandBlind($":{cmd}"); // Restore slewing rate to max CommandBlind($":RS"); - // Set tracking state to before - Tracking = _trackingPriorToMove; + // Restore tracking state saved when this axis started moving + bool savedTracking = Axis == TelescopeAxes.axisPrimary ? _trackingPriorToMovePrimary : _trackingPriorToMoveSecondary; + Tracking = savedTracking; } else { cmd = "S"; - _trackingPriorToMove = Tracking; + // Save current tracking state per-axis before starting the move + bool currentTracking = Tracking; + if (Axis == TelescopeAxes.axisPrimary) + _trackingPriorToMovePrimary = currentTracking; + else + _trackingPriorToMoveSecondary = currentTracking; double rate = Math.Abs(Rate); string rateCommandParam = "GCMS"; int index = 0; @@ -810,20 +815,17 @@ public PierSide SideOfPier { get { - PierSide retVal; - if (SiderealTime < 12) - { - if (RightAscension >= SiderealTime && RightAscension <= SiderealTime + 12) - retVal = PierSide.pierWest; - else - retVal = PierSide.pierEast; - } - else if (RightAscension <= SiderealTime && RightAscension >= SiderealTime - 12) - retVal = PierSide.pierEast; - else - retVal = PierSide.pierWest; + // Read once to avoid multiple serial round-trips and to keep the comparison consistent. + double lst = SiderealTime; + double ra = RightAscension; + + // Compute how far the RA is ahead of the LST, wrapping correctly around 0h/24h. + // hourAngle > 0 means the object has already crossed the meridian (telescope pointing west = pier east). + // hourAngle in [0, 12): pier east. hourAngle in [12, 24): pier west. + double hourAngle = (lst - ra + 24.0) % 24.0; + PierSide retVal = hourAngle < 12.0 ? PierSide.pierEast : PierSide.pierWest; - LogMessage(LoggingFlags.Scope, $"SideOfPier Get => {Enum.GetName(typeof(PierSide), retVal)}"); + LogMessage(LoggingFlags.Scope, $"SideOfPier Get => {Enum.GetName(typeof(PierSide), retVal)} (LST={lst:0.000}, RA={ra:0.000}, HA={hourAngle:0.000})"); return retVal; } set @@ -839,12 +841,9 @@ public double SiderealTime { // now using novas 3.1 double lst = 0.0; - using (ASCOM.Astrometry.NOVAS.NOVAS31 novas = new ASCOM.Astrometry.NOVAS.NOVAS31()) - { - double jd = _utilities.DateUTCToJulian(DateTime.UtcNow); - novas.SiderealTime(jd, 0, novas.DeltaT(jd), ASCOM.Astrometry.GstType.GreenwichMeanSiderealTime, - ASCOM.Astrometry.Method.EquinoxBased, ASCOM.Astrometry.Accuracy.Reduced, ref lst); - } + double jd = _utilities.DateUTCToJulian(DateTime.UtcNow); + _novas31.SiderealTime(jd, 0, _novas31.DeltaT(jd), ASCOM.Astrometry.GstType.GreenwichMeanSiderealTime, + ASCOM.Astrometry.Method.EquinoxBased, ASCOM.Astrometry.Accuracy.Reduced, ref lst); // Allow for the longitude lst += SiteLongitude / 360.0 * 24.0; @@ -874,8 +873,8 @@ public double SiteElevation LogMessage(LoggingFlags.Scope, $"SiteElevation Setting from {profile.Elevation:0.00} to {value:0.00}"); profile.Elevation = value; SharedResources.WriteProfile(profile); - _transform.SiteElevation = SiteElevation; - _azAltTransform.SiteElevation = SiteElevation; + _transform.SiteElevation = value; + _azAltTransform.SiteElevation = value; } } else @@ -967,28 +966,38 @@ public long FirmwareVersion } LogMessage(LoggingFlags.Scope, "FirmwareVersion Get"); - if (_fwVersion == 0) + if (Interlocked.Read(ref _fwVersion) == 0) { - var version = CommandString(":GVN#,#"); - var versionNumbers = version.Substring(1).Split(".".ToCharArray()); - if (versionNumbers.Length != 3) - { - LogMessage(LoggingFlags.Scope, $"Unrecognizable firmware version '{version}'"); - } - else + lock (_fwVersionLock) { - try + if (_fwVersion == 0) // second check inside lock { - _fwVersion = long.Parse(versionNumbers[0]) * 10000L + long.Parse(versionNumbers[1]) * 100L + long.Parse(versionNumbers[2]); - } - catch - { - LogMessage(LoggingFlags.Scope, $"Unable to parse firmware version '{version}'"); + var version = CommandString(":GVN#,#"); + var versionNumbers = version.Substring(1).Split(".".ToCharArray()); + if (versionNumbers.Length != 3) + { + LogMessage(LoggingFlags.Scope, $"Unrecognizable firmware version '{version}'"); + } + else + { + try + { + Interlocked.Exchange(ref _fwVersion, + long.Parse(versionNumbers[0]) * 10000L + + long.Parse(versionNumbers[1]) * 100L + + long.Parse(versionNumbers[2])); + } + catch + { + LogMessage(LoggingFlags.Scope, $"Unable to parse firmware version '{version}'"); + } + } } } } - LogMessage(LoggingFlags.Scope, $"FirmwareVersion Get => {_fwVersion}"); - return _fwVersion; + long fwv = Interlocked.Read(ref _fwVersion); + LogMessage(LoggingFlags.Scope, $"FirmwareVersion Get => {fwv}"); + return fwv; } } @@ -1130,7 +1139,7 @@ public void SyncToCoordinates(double RightAscension, double Declination) private bool ValidateCoordinates(double rightAscension, double declination) { - return rightAscension <= 24 && rightAscension >= 0 && declination >= -90 && declination <= 90; + return rightAscension >= 0 && rightAscension < 24 && declination >= -90 && declination <= 90; } public void SyncToTarget() @@ -1247,12 +1256,13 @@ public DriveRates TrackingRate } set { - LogMessage(LoggingFlags.Scope, $"TrackingRate Set - Ignoring value {value}. Only sidereal supported."); - driveRate = DriveRates.driveSidereal; + LogMessage(LoggingFlags.Scope, $"TrackingRate Set - {value}"); if (value != DriveRates.driveSidereal) { + LogMessage(LoggingFlags.Scope, $"TrackingRate Set - rejecting {value}, only sidereal supported."); throw new InvalidValueException("Only sidereal tracking rate supported."); } + driveRate = DriveRates.driveSidereal; } } @@ -1272,10 +1282,26 @@ public DateTime UTCDate get { string localDate = CommandString(":GC#,#"); // mm/dd/yy - string localTime= CommandString(":GL#,#"); // HH:MM:SS in 24h format - DateTime now = DateTime.ParseExact(localDate + " " + localTime, "MM/dd/yy HH:mm:ss", CultureInfo.InvariantCulture); - DateTime utcDate = now.ToUniversalTime(); - LogMessage(LoggingFlags.Scope, $"UTCDate Get => {utcDate}"); + string localTime = CommandString(":GL#,#"); // HH:MM:SS in 24h format + // Query the UTC offset that was stored in the mount via :SG. + // The mount stores it with an inverted sign convention ('+' = west of UTC / negative, '-' = east of UTC / positive), + // matching what the UTCDate setter and SetupDialog send. + string utcOffsetStr = CommandString(":GG#,#"); + DateTime mountLocalTime = DateTime.ParseExact(localDate + " " + localTime, "MM/dd/yy HH:mm:ss", CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None); + + double offsetHours = 0; + if (utcOffsetStr.Length >= 2 && double.TryParse(utcOffsetStr.Substring(1), System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture, out double absOffset)) + { + // Invert the sign back: stored '+' means west (negative offset), stored '-' means east (positive offset). + offsetHours = (utcOffsetStr[0] == '+') ? -absOffset : absOffset; + } + else + { + LogMessage(LoggingFlags.Scope, $"UTCDate Get - could not parse UTC offset '{utcOffsetStr}', defaulting to 0"); + } + + DateTime utcDate = mountLocalTime.AddHours(-offsetHours); + LogMessage(LoggingFlags.Scope, $"UTCDate Get => {utcDate} (mount local: {mountLocalTime}, stored offset string: '{utcOffsetStr}', applied offset: {offsetHours}h)"); return utcDate; } set @@ -1353,20 +1379,26 @@ private void CheckConnected(string message) throw new NotConnectedException(message); } - private int PollUntilZero(string command) + private int PollUntilZero(string command, int timeoutSeconds = 60) { - // Takes a command to be sent via CommandString, and resends every 1000ms until a 0 is returned. Returns 0 only when complete. + // Sends command every 500ms until "0" is returned, indicating completion. + // Throws DriverException if the mount does not respond with "0" within timeoutSeconds. + var deadline = DateTime.UtcNow.AddSeconds(timeoutSeconds); string retVal = ""; while (retVal != "0") { + if (DateTime.UtcNow > deadline) + { + LogMessage(LoggingFlags.Scope, $"PollUntilZero - Timed out after {timeoutSeconds}s waiting for command: {command}"); + throw new ASCOM.DriverException($"Mount did not complete operation within {timeoutSeconds} seconds (command: {command})"); + } retVal = CommandString(command); LogMessage(LoggingFlags.Scope, $"PollUntilZero - Command: {command}, Response: {retVal}"); - if (retVal == "0") - break; - Thread.Sleep(1000); + if (retVal != "0") + Thread.Sleep(500); } - return System.Convert.ToInt32(retVal); + return 0; } private void LogMessage(LoggingFlags flags, string message) diff --git a/ASCOM.Driver/TelescopeDriver/FocuserDriver.cs b/ASCOM.Driver/TelescopeDriver/FocuserDriver.cs index 85dd56c..90b127f 100644 --- a/ASCOM.Driver/TelescopeDriver/FocuserDriver.cs +++ b/ASCOM.Driver/TelescopeDriver/FocuserDriver.cs @@ -118,6 +118,7 @@ public void SetupDialog() if (IsConnected) { MessageBox.Show("OAT is connected, use Telescope driver to control focuser manually."); + return; } using (var f = new SetupDialogForm(Profile, null, null)) @@ -146,8 +147,6 @@ public string Action(string actionName, string actionParameters) public void CommandBlind(string command, bool raw) { - SharedResources.OATCommandMutex.WaitOne(); - try { SharedResources.SendMessage(command); @@ -156,10 +155,6 @@ public void CommandBlind(string command, bool raw) { logMessage(LoggingFlags.Focuser, $"CommandBlind - Exception{ex.Message}"); } - finally - { - SharedResources.OATCommandMutex.ReleaseMutex(); - } } public bool CommandBool(string command, bool raw) @@ -169,8 +164,6 @@ public bool CommandBool(string command, bool raw) public string CommandString(string command, bool raw) { - SharedResources.OATCommandMutex.WaitOne(); - try { var response = SharedResources.SendMessage(command); @@ -181,10 +174,6 @@ public string CommandString(string command, bool raw) logMessage(LoggingFlags.Focuser, $"CommandString({command}) => Exception {ex.Message}"); return "255"; } - finally - { - SharedResources.OATCommandMutex.ReleaseMutex(); - } } public bool Connected @@ -332,11 +321,16 @@ public int MaxStep public void Move(int newPosition) { + // NOTE: The OAT firmware (:FM) only supports relative moves. We emulate absolute positioning + // by reading the current position and computing the delta. This is not atomic — if the focuser + // is still moving when Move() is called, the read position will be stale and the final position + // will be wrong. Callers should check IsMoving before calling Move() to avoid this. logMessage(LoggingFlags.Focuser, $"MoveAbs To({newPosition})"); int currentPosition = Convert.ToInt32(CommandString($":Fp#,#", false)); int moveBy = newPosition - currentPosition; + logMessage(LoggingFlags.Focuser, $"MoveAbs To({newPosition}) - current={currentPosition}, delta={moveBy}"); CommandBlind($":FM{moveBy}#", false); - logMessage(LoggingFlags.Focuser, $"MoveAbs To({newPosition}) - complete"); + logMessage(LoggingFlags.Focuser, $"MoveAbs To({newPosition}) - command sent"); } public int Position diff --git a/ASCOM.Driver/TelescopeDriver/Properties/AssemblyInfo.cs b/ASCOM.Driver/TelescopeDriver/Properties/AssemblyInfo.cs index d7be0f5..c85f37d 100644 --- a/ASCOM.Driver/TelescopeDriver/Properties/AssemblyInfo.cs +++ b/ASCOM.Driver/TelescopeDriver/Properties/AssemblyInfo.cs @@ -8,11 +8,11 @@ // // TODO - Add your authorship information here [assembly: AssemblyTitle("ASCOM.OpenAstroTracker.Telescope")] -[assembly: AssemblyDescription("ASCOM Telescope and Focuser driver for OpenAstroTracker")] +[assembly: AssemblyDescription("ASCOM Telescope and Focuser driver for OpenAstroTech Mounts")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("OpenAstroTech")] -[assembly: AssemblyProduct("OpenAstroTracker")] -[assembly: AssemblyCopyright("Copyright © 2020-2025 OpenAstroTech")] +[assembly: AssemblyProduct("OpenAstroTech Mounts")] +[assembly: AssemblyCopyright("Copyright © 2020-2026 OpenAstroTech")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -35,5 +35,5 @@ // by using the '*' as shown below: // // TODO - Set your driver's version here -[assembly: AssemblyVersion("6.6.7.3")] -[assembly: AssemblyFileVersion("6.6.7.3")] +[assembly: AssemblyVersion("6.6.8.0")] +[assembly: AssemblyFileVersion("6.6.8.0")] diff --git a/ASCOM.Driver/TelescopeDriver/SetupDialogForm.cs b/ASCOM.Driver/TelescopeDriver/SetupDialogForm.cs index 6c8b26f..57ca5f8 100644 --- a/ASCOM.Driver/TelescopeDriver/SetupDialogForm.cs +++ b/ASCOM.Driver/TelescopeDriver/SetupDialogForm.cs @@ -383,6 +383,18 @@ private void btnConnect_Click(object sender, EventArgs e) try { this._oat.Connected = true; + } + catch (Exception ex) + { + btnConnect.Text = "Connect"; + lblStatus.Text = "Connection failed"; + lblStatus.Update(); + MessageBox.Show("OATControl was unable to connect to OAT.\n\nMessage: " + ex.Message + "\n\nIf this was the first connection attempt after connecting, please try again.", "Connection failed!", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + try + { string fwVersion = this._oat.Action("Serial:PassThroughCommand", ":GVN#,#"); if (string.IsNullOrEmpty(fwVersion)) { @@ -447,7 +459,7 @@ private void btnConnect_Click(object sender, EventArgs e) else if (hwParts[i].StartsWith("INFO_")) { var infoParts = hwParts[i].Split('_'); - if (infoParts.Length == 3) + if (infoParts.Length == 4) { scopeFeatures.Add(String.Format("{0} Info ({1} on {2})", infoParts[3], infoParts[2], infoParts[1])); } @@ -527,7 +539,7 @@ private void btnConnect_Click(object sender, EventArgs e) btnConnect.Text = "Connect"; lblStatus.Text = "Connection failed"; lblStatus.Update(); - MessageBox.Show("OATControl was unable to connect to OAT.\n\nMessage: " + ex.Message + "\n\nIf this was the first connection attempt after connecting, please try again.", "Connection failed!", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show("OAT connected but failed to read configuration.\n\nMessage: " + ex.Message, "Configuration error!", MessageBoxButtons.OK, MessageBoxIcon.Error); } } else @@ -635,6 +647,12 @@ private void btnUnparkDEC_Click(object sender, EventArgs e) string status = this._oat.Action("Serial:PassThroughCommand", ":GX#,#"); _logger($"Unpark - GX returned {status}"); var statusParts = status.Split(','); + if (statusParts.Length < 4) + { + _logger($"Unpark - unexpected GX response: '{status}'"); + MessageBox.Show("Unexpected response from mount. Please try again.", "Unparking", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } _logger($"Unpark - DEC is {statusParts[3]}"); long decSteps = long.Parse(statusParts[3], _oatCulture); if (decSteps == 0) @@ -666,6 +684,12 @@ private void btnParkDEC_Click(object sender, EventArgs e) string status = this._oat.Action("Serial:PassThroughCommand", ":GX#,#"); _logger($"Park - GX returned {status}"); var statusParts = status.Split(','); + if (statusParts.Length < 4) + { + _logger($"Park - unexpected GX response: '{status}'"); + MessageBox.Show("Unexpected response from mount. Please try again.", "Parking", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } _logger($"Park - DEC is {statusParts[3]}"); long decSteps = long.Parse(statusParts[3], _oatCulture); if (decSteps == 0) @@ -704,6 +728,12 @@ private void btnSetHome_Click(object sender, EventArgs e) string status = this._oat.Action("Serial:PassThroughCommand", ":GX#,#"); _logger($"GX returned {status}"); var statusParts = status.Split(','); + if (statusParts.Length < 4) + { + _logger($"SetHome - unexpected GX response: '{status}'"); + MessageBox.Show("Unexpected response from mount. Please try again.", "Set Home", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } _logger($"DEC is {statusParts[3]}"); long decSteps = long.Parse(statusParts[3], _oatCulture); if (decSteps != 0) @@ -798,6 +828,14 @@ private void timerMountUpdate_Tick(object sender, EventArgs e) timerMountUpdate.Stop(); string status = this._oat.Action("Serial:PassThroughCommand", ":GX#,#"); var statusParts = status.Split(','); + + if (statusParts.Length < 7) + { + // Malformed response — restart timer and wait for next tick + timerMountUpdate.Start(); + return; + } + lblStatus.Text = statusParts[0]; string lst = _oat.Action("Serial:PassThroughCommand", ":XGL#,#"); @@ -806,12 +844,21 @@ private void timerMountUpdate_Tick(object sender, EventArgs e) lst = $"{lst.Substring(0, 2)}:{lst.Substring(2, 2)}:{lst.Substring(4)}"; } lblLST.Text = lst; - lblRACoordinate.Text = $"{statusParts[5].Substring(0, 2)}h {statusParts[5].Substring(2, 2)}m {statusParts[5].Substring(4, 2)}s"; - lblDECCoordinate.Text = $"{statusParts[6].Substring(1, 2)}° {statusParts[6].Substring(3, 2)}\" {statusParts[6].Substring(5, 2)}'"; + + string raPart = statusParts[5]; + lblRACoordinate.Text = raPart.Length >= 6 + ? $"{raPart.Substring(0, 2)}h {raPart.Substring(2, 2)}m {raPart.Substring(4, 2)}s" + : raPart; + + string decPart = statusParts[6]; + lblDECCoordinate.Text = decPart.Length >= 7 + ? $"{decPart.Substring(1, 2)}° {decPart.Substring(3, 2)}\" {decPart.Substring(5, 2)}'" + : decPart; + lblRAPosition.Text = statusParts[2]; lblDECPosition.Text = statusParts[3]; lblTRKPosition.Text = statusParts[4]; - if (statusParts.Length > 8) + if (statusParts.Length > 7) { lblFocusPosition.Text = statusParts[7]; }