diff --git a/ASCOM.Driver/OpenAstroTracker/LocalServer.cs b/ASCOM.Driver/OpenAstroTracker/LocalServer.cs
index f4fa7f4..ea563a5 100644
--- a/ASCOM.Driver/OpenAstroTracker/LocalServer.cs
+++ b/ASCOM.Driver/OpenAstroTracker/LocalServer.cs
@@ -397,7 +397,7 @@ private static void RegisterObjects()
key.CreateSubKey("Programmable");
using (RegistryKey key2 = key.CreateSubKey("LocalServer32"))
{
- key2.SetValue(null, Application.ExecutablePath);
+ key2.SetValue(null, "\"" + Application.ExecutablePath + "\"");
}
}
//
diff --git a/ASCOM.Driver/OpenAstroTracker/OpenAstroTracker.csproj b/ASCOM.Driver/OpenAstroTracker/OpenAstroTracker.csproj
index fbdd447..832f8be 100644
--- a/ASCOM.Driver/OpenAstroTracker/OpenAstroTracker.csproj
+++ b/ASCOM.Driver/OpenAstroTracker/OpenAstroTracker.csproj
@@ -42,7 +42,7 @@
DEBUG;TRACE
prompt
4
- AnyCPU
+ x86
MinimumRecommendedRules.ruleset
@@ -62,37 +62,37 @@
- packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Astrometry.dll
+ ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Astrometry.dll
- packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Attributes.dll
+ ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Attributes.dll
- packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Cache.dll
+ ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Cache.dll
- packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Controls.dll
+ ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Controls.dll
- packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.DeviceInterfaces.dll
+ ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.DeviceInterfaces.dll
- packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.DriverAccess.dll
+ ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.DriverAccess.dll
- packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Exceptions.dll
+ ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Exceptions.dll
- packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Internal.Extensions.dll
+ ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Internal.Extensions.dll
- packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.SettingsProvider.dll
+ ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.SettingsProvider.dll
- packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Utilities.dll
+ ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Utilities.dll
- packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Utilities.Video.dll
+ ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Utilities.Video.dll
diff --git a/ASCOM.Driver/OpenAstroTracker/SharedResources.cs b/ASCOM.Driver/OpenAstroTracker/SharedResources.cs
index a09c77b..fb1ca40 100644
--- a/ASCOM.Driver/OpenAstroTracker/SharedResources.cs
+++ b/ASCOM.Driver/OpenAstroTracker/SharedResources.cs
@@ -250,6 +250,10 @@ public static string SendMessage(string message)
else
{
LogMessage(LoggingFlags.Serial, $"SendMessage Nr{messageNr,0:0000} - Not connected or Empty Message: " + message);
+ if (!SharedSerial.Connected)
+ {
+ throw new ASCOM.NotConnectedException($"SendMessage called while serial port is disconnected. Command: {message}");
+ }
}
LogMessage(LoggingFlags.Serial, $"SendMessage Nr{messageNr,0:0000} - Releasing lock");
}
diff --git a/ASCOM.Driver/TelescopeDriver/Driver.cs b/ASCOM.Driver/TelescopeDriver/Driver.cs
index bd7e547..e7c29b9 100644
--- a/ASCOM.Driver/TelescopeDriver/Driver.cs
+++ b/ASCOM.Driver/TelescopeDriver/Driver.cs
@@ -83,8 +83,23 @@ public Telescope()
/// ''' the new settings are saved, otherwise the old values are reloaded.
/// ''' THIS IS THE ONLY PLACE WHERE SHOWING USER INTERFACE IS ALLOWED!
/// '''
+ [System.Runtime.InteropServices.DllImport("user32.dll")]
+ private static extern bool SetForegroundWindow(IntPtr hWnd);
+
public void SetupDialog()
{
+ // When started by COM (-embedding), Windows denies this process foreground
+ // privilege, so SetupDialogForm.ShowDialog() opens but never gets focus.
+ // Call SetForegroundWindow on the main form's handle to regain foreground
+ // status before showing the modal setup dialog. SetupDialog() runs on the
+ // main STA thread, so we can call these APIs directly.
+ var mainForm = System.Windows.Forms.Application.OpenForms.Count > 0
+ ? System.Windows.Forms.Application.OpenForms[0] : null;
+ if (mainForm != null)
+ {
+ mainForm.WindowState = System.Windows.Forms.FormWindowState.Normal;
+ SetForegroundWindow(mainForm.Handle);
+ }
using (var f = new SetupDialogForm(Profile, this, (s) => this.LogMessage(LoggingFlags.Setup, s)))
{
if (f.ShowDialog() == DialogResult.OK)
@@ -93,6 +108,9 @@ public void SetupDialog()
SharedResources.SetTraceFlags(Profile.TraceFlags);
}
}
+ // Re-minimize frmMain after setup dialog is dismissed
+ if (mainForm != null && Server.StartedByCOM)
+ mainForm.WindowState = System.Windows.Forms.FormWindowState.Minimized;
}
public ArrayList SupportedActions
@@ -1353,19 +1371,28 @@ private void CheckConnected(string message)
throw new NotConnectedException(message);
}
- private int PollUntilZero(string command)
+ private int PollUntilZero(string command, int maxAttempts = 120)
{
// Takes a command to be sent via CommandString, and resends every 1000ms until a 0 is returned. Returns 0 only when complete.
+ // maxAttempts caps the wait to prevent hanging indefinitely if the mount stalls or communication fails (default: 120s).
string retVal = "";
- while (retVal != "0")
+ int attempts = 0;
+ while (retVal != "0" && attempts < maxAttempts)
{
retVal = CommandString(command);
- LogMessage(LoggingFlags.Scope, $"PollUntilZero - Command: {command}, Response: {retVal}");
+ LogMessage(LoggingFlags.Scope, $"PollUntilZero - Command: {command}, Response: {retVal}, Attempt: {attempts + 1}/{maxAttempts}");
if (retVal == "0")
break;
+ attempts++;
Thread.Sleep(1000);
}
+ if (attempts >= maxAttempts)
+ {
+ LogMessage(LoggingFlags.Scope, $"PollUntilZero - Timed out after {maxAttempts} attempts for command: {command}");
+ throw new System.TimeoutException($"Mount did not complete operation within {maxAttempts} seconds. Command: {command}");
+ }
+
return System.Convert.ToInt32(retVal);
}
diff --git a/OATCommunications/CommunicationHandlers/TcpCommunicationHandler.cs b/OATCommunications/CommunicationHandlers/TcpCommunicationHandler.cs
index 3922287..216be27 100644
--- a/OATCommunications/CommunicationHandlers/TcpCommunicationHandler.cs
+++ b/OATCommunications/CommunicationHandlers/TcpCommunicationHandler.cs
@@ -1,4 +1,4 @@
-using OATCommunications.ClientAdapters;
+using OATCommunications.ClientAdapters;
using OATCommunications.Utilities;
using System;
using System.Collections.Generic;
@@ -16,6 +16,7 @@ public class TcpCommunicationHandler : CommunicationHandler
private IPAddress _ip;
private int _port;
private TcpClient _client;
+ private NetworkStream _stream;
private List _available;
private Action _addCallback;
@@ -67,12 +68,17 @@ protected override void RunJob(Job job)
while ((attempt < 4) && (_client != null))
{
Log.WriteLine("TCP: [{0}] Attempt {1} to send command.", command, attempt);
- if (!_client.Connected)
+
+ // Only (re)connect when the stream has been closed due to an error, or on first use.
+ if (_stream == null)
{
try
{
_client = new TcpClient();
_client.Connect(_ip, _port);
+ _client.ReceiveTimeout = 1000;
+ _client.SendTimeout = 1000;
+ _stream = _client.GetStream();
}
catch (Exception e)
{
@@ -82,21 +88,19 @@ protected override void RunJob(Job job)
}
}
- _client.ReceiveTimeout = 1000;
- _client.SendTimeout = 1000;
-
string error = String.Empty;
- var stream = _client.GetStream();
var bytes = Encoding.ASCII.GetBytes(command);
try
{
- stream.Write(bytes, 0, bytes.Length);
+ _stream.Write(bytes, 0, bytes.Length);
Log.WriteLine("TCP: [{0}] Sent command!", command);
}
catch (Exception e)
{
Log.WriteLine("TCP: [{0}] Unable to write command to stream: {1}", command, e.Message);
+ _stream.Close();
+ _stream = null;
job.OnFulFilled(new CommandResponse("", false, $"Failed to send message: {e.Message}"));
return;
}
@@ -110,13 +114,12 @@ protected override void RunJob(Job job)
Log.WriteLine("TCP: [{0}] No reply needed to command", command);
break;
- case ResponseType.DoubleFullResponse:
case ResponseType.DigitResponse:
case ResponseType.FullResponse:
{
Log.WriteLine("TCP: [{0}] Expecting a {1} reply to command, waiting...", command, job.ResponseType.ToString());
var response = new byte[256];
- var respCount = stream.Read(response, 0, response.Length);
+ var respCount = _stream.Read(response, 0, response.Length);
respString = Encoding.ASCII.GetString(response, 0, respCount);
Log.WriteLine("TCP: [{0}] Received reply to command -> [{1}], trimming", command, respString);
int hashPos = respString.IndexOf('#');
@@ -128,22 +131,43 @@ protected override void RunJob(Job job)
attempt = 10;
}
break;
+
+ case ResponseType.DoubleFullResponse:
+ {
+ Log.WriteLine("TCP: [{0}] Expecting a DoubleFullResponse reply to command, waiting...", command);
+ var response = new byte[256];
+ var respCount = _stream.Read(response, 0, response.Length);
+ respString = Encoding.ASCII.GetString(response, 0, respCount);
+ Log.WriteLine("TCP: [{0}] Received first reply to command -> [{1}], trimming", command, respString);
+ int hashPos = respString.IndexOf('#');
+ if (hashPos > 0)
+ {
+ respString = respString.Substring(0, hashPos);
+ }
+ Log.WriteLine("TCP: [{0}] Returning first reply to command -> [{1}]", command, respString);
+ // Read and discard the second response
+ var response2 = new byte[256];
+ _stream.Read(response2, 0, response2.Length);
+ attempt = 10;
+ }
+ break;
}
}
catch (Exception e)
{
Log.WriteLine("TCP: [{0}] Failed to read reply to command. {1} thrown", command, e.GetType().Name);
- if (job.ResponseType != ResponseType.NoResponse)
- {
- respString = "0#";
- }
+ _stream.Close();
+ _stream = null;
+ respString = string.Empty;
}
- stream.Close();
attempt++;
+ // Stream is intentionally left open for the next command.
}
- job.OnFulFilled(new CommandResponse(respString));
+ bool succeeded = job.ResponseType == ResponseType.NoResponse || !string.IsNullOrEmpty(respString);
+ job.OnFulFilled(new CommandResponse(respString, succeeded, succeeded ? string.Empty : $"Failed to read reply to [{command}]"));
+
}
public override bool Connected
@@ -185,6 +209,11 @@ public override void Disconnect()
waitQuit.WaitOne();
Log.WriteLine("TCP: Closing port.");
+ if (_stream != null)
+ {
+ _stream.Close();
+ _stream = null;
+ }
_client.Close();
_client = null;
Log.WriteLine("TCP: Disconnected...");
diff --git a/OATCommunications/TelescopeCommandHandlers/OatmealTelescopeCommandHandlers.cs b/OATCommunications/TelescopeCommandHandlers/OatmealTelescopeCommandHandlers.cs
index 5fa9dcd..eec474c 100644
--- a/OATCommunications/TelescopeCommandHandlers/OatmealTelescopeCommandHandlers.cs
+++ b/OATCommunications/TelescopeCommandHandlers/OatmealTelescopeCommandHandlers.cs
@@ -43,6 +43,7 @@ public async Task RefreshMountState()
MountState.Declination = GetCompactDec(parts[6]);
success = true;
}
+ doneEvent.Set();
});
await doneEvent.WaitAsync();
@@ -273,6 +274,7 @@ public async Task Slew(TelescopePosition position)
SendCommand($":MS#,n", (moveResult) =>
{
success = success && moveResult.Success && moveResult.Data == "1";
+ doneEvent.Set();
});
await doneEvent.WaitAsync();
@@ -361,8 +363,6 @@ public async Task SetLocation(double lat, double lon, double altitudeInMet
bool success = false;
AsyncAutoResetEvent doneEvent = new AsyncAutoResetEvent();
-
- await doneEvent.WaitAsync();
// Longitude
success = await SetSiteLongitude((float)lon) == "1";