Files
MeadeGeneric/Meade.net.Telescope/Telescope.cs
T
ColinD 21e7bcd530 Added support for the GW Command.
Removed the implementation of Tracking Set as this does not do anything in the code.
Set Can Set Tracking to false.
Get tracking always returns true is the GW command is not supported.
2021-03-10 19:25:27 +00:00

2602 lines
97 KiB
C#

#define Telescope
using System;
using System.Collections;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using ASCOM.Astrometry;
using ASCOM.Astrometry.AstroUtils;
using ASCOM.Astrometry.NOVAS;
using ASCOM.DeviceInterface;
using ASCOM.Meade.net.AstroMaths;
using ASCOM.Meade.net.Properties;
using ASCOM.Meade.net.Wrapper;
using ASCOM.Utilities;
using ASCOM.Utilities.Interfaces;
namespace ASCOM.Meade.net
{
//
// Your driver's DeviceID is ASCOM.Meade.net.Telescope
//
// The Guid attribute sets the CLSID for ASCOM.Meade.net.Telescope
// The ClassInterface/None addribute prevents an empty interface called
// _Meade.net from being created and used as the [default] interface
//
// Replace the not implemented exceptions with code to implement the function or
// throw the appropriate ASCOM exception.
//
/// <summary>
/// ASCOM Telescope Driver for Meade.net.
/// </summary>
[Guid("d9fd4b3e-c4f1-48ac-a16f-d02eef30d86f")]
[ProgId("ASCOM.MeadeGeneric.Telescope")]
[ServedClassName("Meade Generic")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class Telescope : MeadeTelescopeBase, ITelescopeV3
{
/// <summary>
/// ASCOM DeviceID (COM ProgID) for this driver.
/// The DeviceID is used by ASCOM applications to load the driver at runtime.
/// </summary>
//internal static string driverID = "ASCOM.Meade.net.Telescope";
private static readonly string DriverId = Marshal.GenerateProgIdForType(MethodBase.GetCurrentMethod().DeclaringType ?? throw new System.InvalidOperationException());
/// <summary>
/// Private variable to hold an ASCOM Utilities object
/// </summary>
private readonly IUtil _utilities;
private readonly IUtilExtra _utilitiesExtra;
/// <summary>
/// Private variable to hold an ASCOM AstroUtilities object to provide the Range method
/// </summary>
private readonly IAstroUtils _astroUtilities;
private readonly IAstroMaths _astroMaths;
private readonly IClock _clock;
/// <summary>
/// Private variable to hold number of decimals for RA
/// </summary>
private int _digitsRa = 2;
/// <summary>
/// Private variable to hold number of decimals for Dec
/// </summary>
private int _digitsDe = 2;
private short _settleTime;
/// <summary>
/// Initializes a new instance of the <see cref="Meade.net"/> class.
/// Must be public for COM registration.
/// </summary>
public Telescope()
{
try
{
//todo move this out to IOC
var util = new Util(); //Initialise util object
_utilities = util;
_utilitiesExtra = util; //Initialise util object
_astroUtilities = new AstroUtils(); // Initialise astro utilities object
_astroMaths = new AstroMaths.AstroMaths();
_clock = new Clock();
Initialise(nameof(Telescope));
}
catch (Exception e)
{
StringBuilder sb = new StringBuilder();
var currentException = e;
while (currentException != null)
{
AppendException(sb, currentException);
currentException = currentException.InnerException;
if (currentException != null)
sb.AppendLine("-----");
}
MessageBox.Show(sb.ToString());
throw;
}
}
private void AppendException(StringBuilder sb, Exception currentException)
{
sb.AppendLine(currentException.Message);
sb.AppendLine();
sb.AppendLine(currentException.StackTrace);
sb.AppendLine();
}
public Telescope( IUtil util, IUtilExtra utilExtra, IAstroUtils astroUtilities, ISharedResourcesWrapper sharedResourcesWrapper, IAstroMaths astroMaths, IClock clock) : base(sharedResourcesWrapper)
{
_clock = clock;
_utilities = util; //Initialise util object
_utilitiesExtra = utilExtra; //Initialise util object
_astroUtilities = astroUtilities; // Initialise astro utilities object
_astroMaths = astroMaths;
Initialise(nameof(Telescope));
}
private bool _isGuiding;
private bool _isTargetCoordinateInitRequired = true;
//
// PUBLIC COM INTERFACE ITelescopeV3 IMPLEMENTATION
//
#region Common properties and methods.
/// <summary>
/// Displays the Setup Dialog form.
/// If the user clicks the OK button to dismiss the form, then
/// the new settings are saved, otherwise the old values are reloaded.
/// THIS IS THE ONLY PLACE WHERE SHOWING USER INTERFACE IS ALLOWED!
/// </summary>
public void SetupDialog()
{
LogMessage("SetupDialog", "Opening setup dialog");
SharedResourcesWrapper.SetupDialog();
ReadProfile();
LogMessage("SetupDialog", "complete");
//// consider only showing the setup dialog if not connected
//// or call a different dialog if connected
//if (IsConnected)
// System.Windows.Forms.MessageBox.Show("Already connected, just press OK");
//using (SetupDialogForm F = new SetupDialogForm())
//{
// var result = F.ShowDialog();
// if (result == System.Windows.Forms.DialogResult.OK)
// {
// WriteProfile(); // Persist device configuration values to the ASCOM Profile store
// }
//}
}
public ArrayList SupportedActions
{
get
{
LogMessage("SupportedActions Get", "Returning empty arraylist");
var supportedActions = new ArrayList {"handbox", "site"};
return supportedActions;
}
}
public string Action(string actionName, string actionParameters)
{
CheckConnected("Action");
switch (actionName.ToLower())
{
case "handbox":
switch (actionParameters.ToLower())
{
//Read the screen
case "readdisplay":
var output = SharedResourcesWrapper.SendString(":ED#");
return output;
//top row of buttons
case "enter":
SharedResourcesWrapper.SendBlind(":EK13#");
break;
case "mode":
SharedResourcesWrapper.SendBlind(":EK9#");
break;
case "longmode":
SharedResourcesWrapper.SendBlind(":EK11#");
break;
case "goto":
SharedResourcesWrapper.SendBlind(":EK24#");
break;
case "0": //light and 0
SharedResourcesWrapper.SendBlind(":EK48#");
break;
case "1":
SharedResourcesWrapper.SendBlind(":EK49#");
break;
case "2":
SharedResourcesWrapper.SendBlind(":EK50#");
break;
case "3":
SharedResourcesWrapper.SendBlind(":EK51#");
break;
case "4":
SharedResourcesWrapper.SendBlind(":EK52#");
break;
case "5":
SharedResourcesWrapper.SendBlind(":EK53#");
break;
case "6":
SharedResourcesWrapper.SendBlind(":EK54#");
break;
case "7":
SharedResourcesWrapper.SendBlind(":EK55#");
break;
case "8":
SharedResourcesWrapper.SendBlind(":EK56#");
break;
case "9":
SharedResourcesWrapper.SendBlind(":EK57#");
break;
case "up":
SharedResourcesWrapper.SendBlind(":EK94#");
break;
case "down":
SharedResourcesWrapper.SendBlind(":EK118#");
break;
case "back":
SharedResourcesWrapper.SendBlind(":EK87#");
break;
case "forward":
SharedResourcesWrapper.SendBlind(":EK69#");
break;
case "?":
SharedResourcesWrapper.SendBlind(":EK63#");
break;
default:
LogMessage("", "Action {0}, parameters {1} not implemented", actionName, actionParameters);
throw new ActionNotImplementedException($"{actionName}({actionParameters})");
}
break;
case "site":
var parames = actionParameters.ToLower().Split(' ');
switch (parames[0])
{
case "count":
return "4";
case "select":
switch (parames[1])
{
case "1":
case "2":
case "3":
case "4":
SelectSite(parames[1].ToInteger());
break;
default:
LogMessage("", "Action {0}, parameters {1} not implemented", actionName,
actionParameters);
throw new InvalidValueException(
$"Site {actionParameters} not allowed, must be between 1 and 4");
}
break;
case "getname":
switch (parames[1])
{
case "1":
case "2":
case "3":
case "4":
return GetSiteName(parames[1].ToInteger());
default:
LogMessage("", "Action {0}, parameters {1} not implemented", actionName,
actionParameters);
throw new InvalidValueException(
$"Site {actionParameters} not allowed, must be between 1 and 4");
}
case "setname":
switch (parames[1])
{
case "1":
case "2":
case "3":
case "4":
var sitename = actionParameters.Substring(actionParameters.Position(' ', 2)).Trim();
SetSiteName(parames[1].ToInteger(), sitename);
break;
default:
LogMessage("", "Action {0}, parameters {1} not implemented", actionName,
actionParameters);
throw new InvalidValueException(
$"Site {actionParameters} not allowed, must be between 1 and 4");
}
break;
default:
throw new InvalidValueException(
$"Site parameters {actionParameters} not known");
}
break;
default:
LogMessage("", "Action {0}, parameters {1} not implemented", actionName, actionParameters);
throw new ActionNotImplementedException($"{actionName}");
}
return string.Empty;
}
public void CommandBlind(string command, bool raw)
{
CheckConnected("CommandBlind");
// Call CommandString and return as soon as it finishes
//this.CommandString(command, raw);
SharedResourcesWrapper.SendBlind(command);
// or
//throw new ASCOM.MethodNotImplementedException("CommandBlind");
// DO NOT have both these sections! One or the other
}
public bool CommandBool(string command, bool raw)
{
CheckConnected("CommandBool");
//string ret = CommandString(command, raw);
// TODO decode the return string and return true or false
// or
throw new MethodNotImplementedException("CommandBool");
// DO NOT have both these sections! One or the other
}
public string CommandString(string command, bool raw)
{
CheckConnected("CommandString");
// it's a good idea to put all the low level communication with the device here,
// then all communication calls this function
// you need something to ensure that only one command is in progress at a time
return SharedResourcesWrapper.SendString(command);
//throw new ASCOM.MethodNotImplementedException("CommandString");
}
public void Dispose()
{
if (Connected)
Connected = false;
// Clean up the tracelogger and util objects
Tl.Enabled = false;
Tl.Dispose();
Tl = null;
}
public bool Connected
{
get
{
LogMessage("Connected", "Get {0}", IsConnected);
return IsConnected;
}
set
{
LogMessage("Connected", "Set {0}", value);
if (value == IsConnected)
return;
if (value)
{
try
{
ReadProfile();
LogMessage("Connected Set", "Connecting to port {0}", ComPort);
var connectionInfo = SharedResourcesWrapper.Connect("Serial", DriverId, Tl);
try
{
LogMessage("Connected Set", $"Connected to port {ComPort}. Product: {SharedResourcesWrapper.ProductName} Version:{SharedResourcesWrapper.FirmwareVersion}");
_userNewerPulseGuiding = IsNewPulseGuidingSupported();
_targetDeclination = InvalidParameter;
_targetRightAscension = InvalidParameter;
_tracking = true;
LogMessage("Connected Set", $"New Pulse Guiding Supported: {_userNewerPulseGuiding}");
IsConnected = true;
if (connectionInfo.SameDevice == 1)
{
LogMessage("Connected Set", "Making first connection telescope adjustments");
//These settings are applied only when the first device connects to the telescope.
SetLongFormat(true);
if (CanSetGuideRates)
{
SetNewGuideRate(GuideRate, "Connect");
}
SetTelescopePrecision("Connect");
}
else
{
LogMessage("Connected Set", $"Skipping first connection telescope adjustments (current connections: {connectionInfo.SameDevice})");
}
var raAndDec = GetTelescopeRaAndDec();
LogMessage("Connected Set", $"Connected OK. Current RA = {_utilitiesExtra.HoursToHMS(raAndDec.RightAscension)} Dec = {_utilitiesExtra.DegreesToDMS(raAndDec.Declination)}");
}
catch (Exception)
{
SharedResourcesWrapper.Disconnect("Serial", DriverId);
throw;
}
}
catch (Exception ex)
{
LogMessage("Connected Set", "Error connecting to port {0} - {1}", ComPort, ex.Message);
}
}
else
{
LogMessage("Connected Set", "Disconnecting from port {0}", ComPort);
SharedResourcesWrapper.Disconnect("Serial", DriverId);
IsConnected = false;
}
}
}
private void SetTelescopePrecision(string propertyName)
{
switch (Precision.ToLower())
{
case "high":
TelescopePointingPrecision(true);
LogMessage(propertyName, "High precision slewing selected");
break;
case "low":
TelescopePointingPrecision(false);
LogMessage(propertyName, "Low precision slewing selected");
break;
default:
LogMessage(propertyName, "Precision slewing unchanged");
break;
}
}
public bool IsNewPulseGuidingSupported()
{
switch (GuidingStyle)
{
case "guide rate slew":
return false;
case "pulse guiding":
return true;
default:
if (SharedResourcesWrapper.ProductName == TelescopeList.Autostar497)
{
return FirmwareIsGreaterThan(TelescopeList.Autostar497_31Ee);
}
if (SharedResourcesWrapper.ProductName == TelescopeList.LX200GPS)
{
return true;
}
return false;
}
}
private bool IsLongFormatSupported()
{
if (SharedResourcesWrapper.ProductName == TelescopeList.LX200CLASSIC)
{
return false;
}
return true;
}
private bool IsGuideRateSettingSupported()
{
if (SharedResourcesWrapper.ProductName == TelescopeList.LX200GPS)
{
return true;
}
return false;
}
private bool FirmwareIsGreaterThan(string minVersion)
{
var currentVersion = SharedResourcesWrapper.FirmwareVersion;
var comparison = string.Compare(currentVersion, minVersion, StringComparison.Ordinal);
return comparison >= 0;
}
private bool IsLongFormat { get; set; }
/// <summary>
/// classic LX200 needs initial set of target coordinates, if it is slewing and the target RA DE coordinates are 0 and differ from the current coordinates
/// </summary>
private bool IsTargetCoordinateInitRequired()
{
if (SharedResourcesWrapper.ProductName != TelescopeList.LX200CLASSIC)
return false;
if (!_isTargetCoordinateInitRequired)
return _isTargetCoordinateInitRequired;
if (!IsConnected)
return true;
if(SharedResourcesWrapper.ProductName != TelescopeList.LX200CLASSIC)
{
_isTargetCoordinateInitRequired = false;
return _isTargetCoordinateInitRequired;
}
const double eps = 0.00001d;
double rightTargetAscension = RightAscension;
//target RA == 0
if (Math.Abs(rightTargetAscension) > eps)
{
_isTargetCoordinateInitRequired = false;
return _isTargetCoordinateInitRequired;
}
double targetDeclination = Declination;
//target DE == 0
if (Math.Abs(targetDeclination) > eps)
{
_isTargetCoordinateInitRequired = false;
return _isTargetCoordinateInitRequired;
}
//target coordinates are equal current coordinates
if((Math.Abs(RightAscension - rightTargetAscension ) <= eps) &&
(Math.Abs(Declination - targetDeclination) <= eps))
{
LogMessage("IsTargetCoordinateInitRequired", $"0 diff -> false");
_isTargetCoordinateInitRequired = false;
return _isTargetCoordinateInitRequired;
}
LogMessage("IsTargetCoordinateInitRequired", $"{_isTargetCoordinateInitRequired}");
return _isTargetCoordinateInitRequired;
}
private void InitTargetCoordinates()
{
try
{
var raAndDec = GetTelescopeRaAndDec();
//when connection the first time the telescope target coordinates should be the current ones.
//for the classic LX200 at least this is not the case, target ra and dec are 0, when switched on.
LogMessage("InitTargetCoordinates", "sync telescope target");
SyncToCoordinates(raAndDec.RightAscension, raAndDec.Declination);
//do it only once
_isTargetCoordinateInitRequired = false;
}
catch (Exception ex)
{
LogMessage("InitTargetCoordinates", $"Error sync telescope position", ex.Message);
}
}
public void SetLongFormat(bool setLongFormat)
{
IsLongFormat = false;
if (!IsLongFormatSupported())
{
LogMessage("SetLongFormat", "Long coordinate format not supported for this mount");
_digitsRa = 1;
_digitsDe = 0;
return;
}
SharedResourcesWrapper.Lock(() =>
{
var result = SharedResourcesWrapper.SendString(":GZ#");
LogMessage("SetLongFormat", $"Get - Azimuth {result}");
//:GZ# Get telescope azimuth
//Returns: DDD*MM.T or DDD*MMSS#
//The current telescope Azimuth depending on the selected precision.
IsLongFormat = result.Length > 6;
if (IsLongFormat != setLongFormat)
{
_utilities.WaitForMilliseconds(500);
SharedResourcesWrapper.SendBlind(":U#");
//:U# Toggle between low/hi precision positions
//Low - RA displays and messages HH:MM.T sDD*MM
//High - Dec / Az / El displays and messages HH:MM: SS sDD*MM:SS
// Returns Nothing
result = SharedResourcesWrapper.SendString(":GZ#");
IsLongFormat = result.Length > 6;
LogMessage("SetLongFormat", $"Get - Azimuth {result}");
if (IsLongFormat == setLongFormat)
LogMessage("SetLongFormat", $"Long coordinate format: {setLongFormat} ");
}
else
{
LogMessage("SetLongFormat", $"Long coordinate format: {setLongFormat} ");
}
});
LogMessage("SetLongFormat", $"Long coordinate format: {setLongFormat} ");
}
private bool TogglePrecision()
{
LogMessage("TogglePrecision", "Toggling slewing precision");
var result = SharedResourcesWrapper.SendChar(":P#");
//:P# Toggles High Precsion Pointing. When High precision pointing is enabled scope will first allow the operator to center a nearby bright star before moving to the actual target.
//Returns: <string>
//HIGH PRECISION Current setting after this command.
//LOW PRECISION Current setting after this command.
int throwAwayCharacters = "LOW PRECISION".Length - 1;
LogMessage("TogglePrecision", $"Result: {result}");
bool highPrecision = false;
switch (result)
{
case "H":
highPrecision = true;
throwAwayCharacters = "HIGH PRECISION".Length - 1;
break;
}
SharedResourcesWrapper.ReadCharacters(throwAwayCharacters);
//Make sure that the buffers are cleared out.
SharedResourcesWrapper.SendBlind("#");
return highPrecision;
}
private void TelescopePointingPrecision(bool high)
{
var currentPrecision = TogglePrecision();
while (currentPrecision != high)
{
currentPrecision = TogglePrecision();
}
}
public void SelectSite(int site)
{
CheckConnectedAndValidateSite(site, "SelectSite");
SharedResourcesWrapper.SendBlind($":W{site}#");
//:W<n>#
//Set current site to<n>, an ASCII digit in the range 1..4
//Returns: Nothing
}
private void CheckConnectedAndValidateSite(int site, string message)
{
CheckConnected(message);
if (site < 1)
throw new ArgumentOutOfRangeException(nameof(site), site,
Resources.Telescope_SelectSite_Site_cannot_be_lower_than_1);
if (site > 4)
throw new ArgumentOutOfRangeException(nameof(site), site,
Resources.Telescope_SelectSite_Site_cannot_be_higher_than_4);
}
private void SetSiteName(int site, string sitename)
{
CheckConnectedAndValidateSite(site, "SetSiteName");
string command;
switch (site)
{
case 1:
command = $":SM{sitename}#";
//:SM<string>#
//Set site 1s name to be<string>.LX200s only accept 3 character strings. Other scopes accept up to 15 characters.
// Returns:
//0 Invalid
//1 - Valid
break;
case 2:
command = $":SN{sitename}#";
//:SN<string>#
//Set site 2s name to be<string>.LX200s only accept 3 character strings. Other scopes accept up to 15 characters.
// Returns:
//0 Invalid
//1 - Valid
break;
case 3:
command = $":SO{sitename}#";
//:SO<string>#
//Set site 3s name to be<string>.LX200s only accept 3 character strings. Other scopes accept up to 15 characters.
// Returns:
//0 Invalid
//1 - Valid
break;
case 4:
command = $":SP{sitename}#";
//:SP<string>#
//Set site 4s name to be<string>.LX200s only accept 3 character strings. Other scopes accept up to 15 characters.
// Returns:
//0 Invalid
//1 - Valid
break;
default:
throw new ArgumentOutOfRangeException(nameof(site), site, Resources.Telescope_GetSiteName_Site_out_of_range);
}
var result = SharedResourcesWrapper.SendChar(command);
if (result != "1")
{
throw new InvalidOperationException("Failed to set site name.");
}
}
private string GetSiteName(int site)
{
CheckConnectedAndValidateSite(site, "GetSiteName");
switch (site)
{
case 1:
return SharedResourcesWrapper.SendString(":GM#");
//:GM# Get Site 1 Name
//Returns: <string>#
//A # terminated string with the name of the requested site.
case 2:
return SharedResourcesWrapper.SendString(":GN#");
//:GN# Get Site 2 Name
//Returns: <string>#
//A # terminated string with the name of the requested site.
case 3:
return SharedResourcesWrapper.SendString(":GO#");
//:GO# Get Site 3 Name
//Returns: <string>#
//A # terminated string with the name of the requested site.
case 4:
return SharedResourcesWrapper.SendString(":GP#");
//:GP# Get Site 4 Name
//Returns: <string>#
//A # terminated string with the name of the requested site.
default:
throw new ArgumentOutOfRangeException(nameof(site), site, Resources.Telescope_GetSiteName_Site_out_of_range);
}
}
public short InterfaceVersion
{
// set by the driver wizard
get
{
LogMessage("InterfaceVersion Get", "3");
return Convert.ToInt16("3");
}
}
public string Name
{
get
{
//string name = "Short driver name - please customise";
//var telescopeProduceName = _sharedResourcesWrapper.SendString(":GVP#");
////:GVP# Get Telescope Product Name
////Returns: <string>#
//var firmwareVersion = _sharedResourcesWrapper.SendString(":GVN#");
////:GVN# Get Telescope Firmware Number
////Returns: dd.d#
//string name = $"{telescopeProduceName} - {firmwareVersion}";
string name = DriverDescription;
LogMessage("Name Get", name);
return name;
}
}
#endregion
#region ITelescope Implementation
public void AbortSlew()
{
CheckConnected("AbortSlew");
LogMessage("AbortSlew", "Aborting slew");
SharedResourcesWrapper.SendBlind(":Q#");
//:Q# Halt all current slewing
//Returns:Nothing
_movingPrimary = false;
_movingSecondary = false;
SetSlewingMinEndTime();
}
public AlignmentModes AlignmentMode
{
get
{
LogMessage("AlignmentMode Get", "Getting alignmode");
CheckConnected("AlignmentMode Get");
if (FirmwareIsGreaterThan(TelescopeList.Autostar497_43Eg))
{
var alignmentStatus = GetScopeAlignmentStatus();
return alignmentStatus.AlignmentMode;
}
else
{
const char ack = (char)6;
//ACK <0x06> Query of alignment mounting mode.
//Returns:
//A If scope in AltAz Mode
//D If scope is currently in the Downloader[Autostar II & Autostar]
//L If scope in Land Mode
//P If scope in Polar Mode
var alignmentString = SharedResourcesWrapper.SendChar(ack.ToString());
AlignmentModes alignmentMode;
switch (alignmentString)
{
case "A":
alignmentMode = AlignmentModes.algAltAz;
break;
case "P":
alignmentMode = AlignmentModes.algPolar;
break;
//case "G":
// alignmentMode = AlignmentModes.algGermanPolar;
// break;
default:
throw new InvalidValueException(
$"unknown alignment returned from telescope: {alignmentString}");
}
LogMessage("AlignmentMode Get", $"alignmode = {alignmentMode}");
return alignmentMode;
}
}
set
{
CheckConnected("AlignmentMode Set");
//todo tidy this up into a better solution that means can :GW#, :AL#, :AA#, & :AP# and checked for Autostar properly
if (!FirmwareIsGreaterThan(TelescopeList.Autostar497_43Eg))
throw new PropertyNotImplementedException("AlignmentMode",true );
switch (value)
{
case AlignmentModes.algAltAz:
SharedResourcesWrapper.SendBlind(":AA#");
//:AA# Sets telescope the AltAz alignment mode
//Returns: nothing
break;
case AlignmentModes.algPolar:
case AlignmentModes.algGermanPolar:
SharedResourcesWrapper.SendBlind(":AP#");
//:AP# Sets telescope to Polar alignment mode
//Returns: nothing
break;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
//:AL# Sets telescope to Land alignment mode
//Returns: nothing
}
}
private AlignmentStatus GetScopeAlignmentStatus()
{
var alignmentString = SharedResourcesWrapper.SendString(":GW#");
//:GW# Get Scope Alignment Status
//Returns: <mount><tracking><alignment>#
// where:
//mount: A - AzEl mounted, P - Equatorially mounted, G - german mounted equatorial
//tracking: T - tracking, N - not tracking
//alignment: 0 - needs alignment, 1 - one star aligned, 2 - two star aligned, 3 - three star aligned.
var alignmentStatus = new AlignmentStatus();
switch (alignmentString[0])
{
case 'A':
alignmentStatus.AlignmentMode = AlignmentModes.algAltAz;
break;
case 'P':
alignmentStatus.AlignmentMode = AlignmentModes.algPolar;
break;
case 'G':
alignmentStatus.AlignmentMode = AlignmentModes.algGermanPolar;
break;
}
alignmentStatus.Tracking = alignmentString[0] == 'T';
switch (alignmentString[1])
{
case '0':
alignmentStatus.Status = Alignment.NeedsAlignment;
break;
case '1':
alignmentStatus.Status = Alignment.OneStarAligned;
break;
case '2':
alignmentStatus.Status = Alignment.TwoStarAligned;
break;
case '3':
alignmentStatus.Status = Alignment.ThreeStarAligned;
break;
}
return alignmentStatus;
}
public double Altitude
{
get
{
CheckConnected("Altitude Get");
var altAz = CalcAltAzFromTelescopeEqData();
LogMessage("Altitude", $"{altAz.Altitude}");
return altAz.Altitude;
//firmware bug in 44Eg, :GA# is returning the dec, not the altitude!
//var result = _sharedResourcesWrapper.SendString(":GA#");
////:GA# Get Telescope Altitude
////Returns: sDD* MM# or sDD*MMSS#
////The current scope altitude. The returned format depending on the current precision setting.
//var alt = utilities.DMSToDegrees(result);
//LogMessage("Altitude", $"{alt}");
//return alt;
//LogMessage("Altitude Get", "Not implemented");
//throw new ASCOM.PropertyNotImplementedException("Altitude", false);
}
}
private HorizonCoordinates CalcAltAzFromTelescopeEqData()
{
var altitudeData = SharedResourcesWrapper.Lock(() => new AltitudeData
{
UtcDateTime = UTCDate,
SiteLongitude = SiteLongitude,
SiteLatitude = SiteLatitude,
EquatorialCoordinates = GetTelescopeRaAndDec()
});
double hourAngle = _astroMaths.RightAscensionToHourAngle(altitudeData.UtcDateTime, altitudeData.SiteLongitude,
altitudeData.EquatorialCoordinates.RightAscension);
var altAz = _astroMaths.ConvertEqToHoz(hourAngle, altitudeData.SiteLatitude, altitudeData.EquatorialCoordinates);
return altAz;
}
private EquatorialCoordinates GetTelescopeRaAndDec()
{
return new EquatorialCoordinates
{
RightAscension = RightAscension,
Declination = Declination
};
}
public double ApertureArea
{
get
{
LogMessage("ApertureArea Get", "Not implemented");
throw new PropertyNotImplementedException("ApertureArea", false);
}
}
public double ApertureDiameter
{
get
{
LogMessage("ApertureDiameter Get", "Not implemented");
throw new PropertyNotImplementedException("ApertureDiameter", false);
}
}
public bool AtHome
{
get
{
LogMessage("AtHome", "Get - " + false);
return false;
}
}
private bool _atPark;
public bool AtPark
{
get
{
LogMessage("AtPark", "Get - " + _atPark);
return _atPark;
}
private set => _atPark = value;
}
public IAxisRates AxisRates(TelescopeAxes axis)
{
LogMessage("AxisRates", "Get - " + axis);
return new AxisRates(axis);
}
public double Azimuth
{
get
{
CheckConnected("Azimuth Get");
//var result = _sharedResourcesWrapper.SendString(":GZ#");
//:GZ# Get telescope azimuth
//Returns: DDD*MM#T or DDD*MMSS#
//The current telescope Azimuth depending on the selected precision.
//double az = utilities.DMSToDegrees(result);
//LogMessage("Azimuth Get", $"{az}");
//return az;
var altAz = CalcAltAzFromTelescopeEqData();
LogMessage("Azimuth Get", $"{altAz.Azimuth}");
return altAz.Azimuth;
}
}
public bool CanFindHome
{
get
{
LogMessage("CanFindHome", "Get - " + false);
return false;
}
}
public bool CanMoveAxis(TelescopeAxes axis)
{
LogMessage("CanMoveAxis", "Get - " + axis);
switch (axis)
{
case TelescopeAxes.axisPrimary: return true; //RA or AZ
case TelescopeAxes.axisSecondary: return true; //Dev or Alt
case TelescopeAxes.axisTertiary: return false; //rotator / derotator
default: throw new InvalidValueException("CanMoveAxis", axis.ToString(), "0 to 2");
}
}
public bool CanPark
{
get
{
LogMessage("CanPark", "Get - " + true);
return true;
}
}
public bool CanPulseGuide
{
get
{
LogMessage("CanPulseGuide", "Get - " + true);
return true;
}
}
public bool CanSetDeclinationRate
{
get
{
LogMessage("CanSetDeclinationRate", "Get - " + false);
return false;
}
}
public bool CanSetGuideRates
{
get
{
CheckConnected("CanSetGuideRates Get");
var canSetGuideRate = IsGuideRateSettingSupported();
LogMessage("CanSetGuideRates", "Get - " + canSetGuideRate);
return canSetGuideRate;
}
}
public bool CanSetPark
{
get
{
LogMessage("CanSetPark", "Get - " + false);
return false;
}
}
public bool CanSetPierSide
{
get
{
LogMessage("CanSetPierSide", "Get - " + false);
return false;
}
}
public bool CanSetRightAscensionRate
{
get
{
LogMessage("CanSetRightAscensionRate", "Get - " + false);
return false;
}
}
public bool CanSetTracking
{
get
{
LogMessage("CanSetTracking", "Get - " + false);
return false;
}
}
public bool CanSlew
{
get
{
LogMessage("CanSlew", "Get - " + true);
return true;
}
}
public bool CanSlewAltAz
{
get
{
LogMessage("CanSlewAltAz", "Get - " + true);
return true;
}
}
public bool CanSlewAltAzAsync
{
get
{
LogMessage("CanSlewAltAzAsync", "Get - " + true);
return true;
}
}
public bool CanSlewAsync
{
get
{
LogMessage("CanSlewAsync", "Get - " + true);
return true;
}
}
public bool CanSync
{
get
{
LogMessage("CanSync", "Get - " + true);
return true;
}
}
public bool CanSyncAltAz
{
get
{
LogMessage("CanSyncAltAz", "Get - " + false);
return false;
}
}
public bool CanUnpark
{
get
{
//todo make this return false for non LX-200 GPS telescopes
LogMessage("CanUnpark", "Get - " + true);
return true;
}
}
public double Declination
{
get
{
CheckConnected("Declination Get");
var result = SharedResourcesWrapper.SendString(":GD#");
//:GD# Get Telescope Declination.
//Returns: sDD*MM# or sDD*MMSS#
//Depending upon the current precision setting for the telescope.
double declination = _utilities.DMSToDegrees(result);
LogMessage("Declination", $"Get - {result} convert to {declination} {_utilitiesExtra.DegreesToDMS(declination, ":", ":")}");
return declination;
}
}
public double DeclinationRate
{
get
{
double declination = 0.0;
LogMessage("DeclinationRate", "Get - " + declination.ToString(CultureInfo.InvariantCulture));
return declination;
}
// ReSharper disable once ValueParameterNotUsed
set
{
LogMessage("DeclinationRate Set", "Not implemented");
throw new PropertyNotImplementedException("DeclinationRate", true);
}
}
public PierSide DestinationSideOfPier(double rightAscension, double declination)
{
LogMessage("DestinationSideOfPier Get", "Not implemented");
throw new MethodNotImplementedException("DestinationSideOfPier");
}
public bool DoesRefraction
{
get
{
LogMessage("DoesRefraction Get", "Not implemented");
throw new PropertyNotImplementedException("DoesRefraction", false);
}
// ReSharper disable once ValueParameterNotUsed
set
{
LogMessage("DoesRefraction Set", "Not implemented");
throw new PropertyNotImplementedException("DoesRefraction", true);
}
}
public EquatorialCoordinateType EquatorialSystem
{
get
{
EquatorialCoordinateType equatorialSystem = EquatorialCoordinateType.equTopocentric;
LogMessage("DeclinationRate", "Get - " + equatorialSystem);
return equatorialSystem;
}
}
public void FindHome()
{
LogMessage("FindHome", "Not implemented");
throw new MethodNotImplementedException("FindHome");
}
public double FocalLength
{
get
{
LogMessage("FocalLength Get", "Not implemented");
throw new PropertyNotImplementedException("FocalLength", false);
}
}
private void SetNewGuideRate(double value, string propertyName)
{
if (!IsGuideRateSettingSupported())
{
LogMessage($"{propertyName} Set", "Not implemented");
throw new PropertyNotImplementedException(propertyName, true);
}
if (!value.InRange(0, 15.0417))
{
throw new InvalidValueException(propertyName, value.ToString(CultureInfo.CurrentCulture), $"{0.ToString(CultureInfo.CurrentCulture)} to {15.0417.ToString(CultureInfo.CurrentCulture)}/sec");
}
LogMessage($"{propertyName} Set", $"Setting new guiderate {value.ToString(CultureInfo.CurrentCulture)} arc seconds/second ({value.ToString(CultureInfo.CurrentCulture)} degrees/second)");
SharedResourcesWrapper.SendBlind($":Rg{value:00.0}#");
//:RgSS.S#
//Set guide rate to +/ -SS.S to arc seconds per second.This rate is added to or subtracted from the current tracking
//Rates when the CCD guider or handbox guider buttons are pressed when the guide rate is selected.Rate shall not exceed
//sidereal speed(approx 15.0417/sec)[Autostar II only]
//Returns: Nothing
//info from RickB says that 15.04107 is a better value for
GuideRate = value;
WriteProfile();
}
private double DegreesPerSecondToArcSecondPerSecond(double value)
{
return value * 3600.0;
}
private double ArcSecondPerSecondToDegreesPerSecond(double value)
{
return value / 3600.0;
}
public double GuideRateDeclination
{
get
{
var degreesPerSecond = ArcSecondPerSecondToDegreesPerSecond(GuideRate);
LogMessage("GuideRateDeclination Get", $"{GuideRate} arc seconds / second = {degreesPerSecond} degrees per second");
return degreesPerSecond;
}
set
{
var newValue = DegreesPerSecondToArcSecondPerSecond(value);
SetNewGuideRate(newValue, "GuideRateDeclination");
}
}
public double GuideRateRightAscension
{
get
{
double degreesPerSecond = ArcSecondPerSecondToDegreesPerSecond(GuideRate);
LogMessage("GuideRateRightAscension Get", $"{GuideRate} arc seconds / second = {degreesPerSecond} degrees per second");
return degreesPerSecond;
}
set
{
var newValue = DegreesPerSecondToArcSecondPerSecond(value);
SetNewGuideRate(newValue, "GuideRateRightAscension");
}
}
public bool IsPulseGuiding
{
get
{
//Todo implement this if I can make the new pulse guiding async
LogMessage("IsPulseGuiding Get", "pulse guiding is synchronous for this driver");
//throw new ASCOM.PropertyNotImplementedException("IsPulseGuiding", false);
return false;
}
}
private bool _movingPrimary;
private bool _movingSecondary;
public void MoveAxis(TelescopeAxes axis, double rate)
{
LogMessage("MoveAxis", $"Axis={axis} rate={rate}");
CheckConnected("MoveAxis");
var absRate = Math.Abs(rate);
switch (absRate)
{
case 0:
//do nothing, it's ok this time as we're halting the slew.
break;
case 1:
SharedResourcesWrapper.SendBlind(":RG#");
//:RG# Set Slew rate to Guiding Rate (slowest)
//Returns: Nothing
break;
case 2:
SharedResourcesWrapper.SendBlind(":RC#");
//:RC# Set Slew rate to Centering rate (2nd slowest)
//Returns: Nothing
break;
case 3:
SharedResourcesWrapper.SendBlind(":RM#");
//:RM# Set Slew rate to Find Rate (2nd Fastest)
//Returns: Nothing
break;
case 4:
SharedResourcesWrapper.SendBlind(":RS#");
//:RS# Set Slew rate to max (fastest)
//Returns: Nothing
break;
default:
throw new InvalidValueException($"Rate {rate} not supported");
}
switch (axis)
{
case TelescopeAxes.axisPrimary:
switch (rate.Compare(0))
{
case ComparisonResult.Equals:
if (!_isGuiding)
{
SetSlewingMinEndTime();
}
_movingPrimary = false;
SharedResourcesWrapper.SendBlind(":Qe#");
//:Qe# Halt eastward Slews
//Returns: Nothing
SharedResourcesWrapper.SendBlind(":Qw#");
//:Qw# Halt westward Slews
//Returns: Nothing
break;
case ComparisonResult.Greater:
SharedResourcesWrapper.SendBlind(":Me#");
//:Me# Move Telescope East at current slew rate
//Returns: Nothing
_movingPrimary = true;
break;
case ComparisonResult.Lower:
SharedResourcesWrapper.SendBlind(":Mw#");
//:Mw# Move Telescope West at current slew rate
//Returns: Nothing
_movingPrimary = true;
break;
}
break;
case TelescopeAxes.axisSecondary:
switch (rate.Compare(0))
{
case ComparisonResult.Equals:
if (!_isGuiding)
{
SetSlewingMinEndTime();
}
_movingSecondary = false;
SharedResourcesWrapper.SendBlind(":Qn#");
//:Qn# Halt northward Slews
//Returns: Nothing
SharedResourcesWrapper.SendBlind(":Qs#");
//:Qs# Halt southward Slews
//Returns: Nothing
break;
case ComparisonResult.Greater:
SharedResourcesWrapper.SendBlind(":Mn#");
//:Mn# Move Telescope North at current slew rate
//Returns: Nothing
_movingSecondary = true;
break;
case ComparisonResult.Lower:
SharedResourcesWrapper.SendBlind(":Ms#");
//:Ms# Move Telescope South at current slew rate
//Returns: Nothing
_movingSecondary = true;
break;
}
break;
default:
throw new InvalidValueException("Can not move this axis.");
}
}
public void Park()
{
LogMessage("Park", "Parking telescope");
CheckConnected("Park");
if (AtPark)
return;
SharedResourcesWrapper.SendBlind(":hP#");
//:hP# Autostar, Autostar II and LX 16Slew to Park Position
//Returns: Nothing
AtPark = true;
}
private bool _userNewerPulseGuiding = true;
public void PulseGuide(GuideDirections direction, int duration)
{
LogMessage("PulseGuide", $"pulse guide direction {direction} duration {duration}");
try
{
CheckConnected("PulseGuide");
if (IsSlewingToTarget())
throw new InvalidOperationException("Unable to PulseGuide whilst slewing to target.");
_isGuiding = true;
try
{
if (_movingPrimary &&
(direction == GuideDirections.guideEast || direction == GuideDirections.guideWest))
throw new InvalidOperationException("Unable to PulseGuide while moving same axis.");
if (_movingSecondary &&
(direction == GuideDirections.guideNorth || direction == GuideDirections.guideSouth))
throw new InvalidOperationException("Unable to PulseGuide while moving same axis.");
var coordinatesBeforeMove = GetTelescopeRaAndDec();
if (_userNewerPulseGuiding && duration < 10000)
{
string d = string.Empty;
switch (direction)
{
case GuideDirections.guideEast:
d = "e";
break;
case GuideDirections.guideNorth:
d = "n";
break;
case GuideDirections.guideSouth:
d = "s";
break;
case GuideDirections.guideWest:
d = "w";
break;
}
LogMessage("PulseGuide", "Using new pulse guiding technique");
SharedResourcesWrapper.SendBlind($":Mg{d}{duration:0000}#");
//:MgnDDDD#
//:MgsDDDD#
//:MgeDDDD#
//:MgwDDDD#
//Guide telescope in the commanded direction(nsew) for the number of milliseconds indicated by the unsigned number
//passed in the command.These commands support serial port driven guiding.
//Returns Nothing
//LX200 Not Supported
_utilities.WaitForMilliseconds(duration);
}
else
{
LogMessage("PulseGuide", "Using old pulse guiding technique");
switch (direction)
{
case GuideDirections.guideEast:
MoveAxis(TelescopeAxes.axisPrimary, 1);
_utilities.WaitForMilliseconds(duration);
MoveAxis(TelescopeAxes.axisPrimary, 0);
break;
case GuideDirections.guideNorth:
MoveAxis(TelescopeAxes.axisSecondary, 1);
_utilities.WaitForMilliseconds(duration);
MoveAxis(TelescopeAxes.axisSecondary, 0);
break;
case GuideDirections.guideSouth:
MoveAxis(TelescopeAxes.axisSecondary, -1);
_utilities.WaitForMilliseconds(duration);
MoveAxis(TelescopeAxes.axisSecondary, 0);
break;
case GuideDirections.guideWest:
MoveAxis(TelescopeAxes.axisPrimary, -1);
_utilities.WaitForMilliseconds(duration);
MoveAxis(TelescopeAxes.axisPrimary, 0);
break;
}
LogMessage("PulseGuide", "Using old pulse guiding technique complete");
}
var coordinatesAfterMove = GetTelescopeRaAndDec();
LogMessage("PulseGuide",
$"Complete Before RA: {_utilitiesExtra.HoursToHMS(coordinatesBeforeMove.RightAscension)} Dec:{_utilitiesExtra.DegreesToDMS(coordinatesBeforeMove.Declination)}");
LogMessage("PulseGuide",
$"Complete After RA: {_utilitiesExtra.HoursToHMS(coordinatesAfterMove.RightAscension)} Dec:{_utilitiesExtra.DegreesToDMS(coordinatesAfterMove.Declination)}");
}
finally
{
_isGuiding = false;
}
}
catch (Exception ex)
{
LogMessage("PulseGuide", $"Error performing pulse guide: {ex.Message}");
throw;
}
}
/// <summary>
/// convert a HH:MM.T (classic LX200 RA Notation) string to a double hours. T is the decimal part of minutes which is converted into seconds
/// </summary>
public double HmToHours(string hm)
{
var token = hm.Split('.');
if (token.Length != 2)
return _utilities.HMSToHours(hm);
var seconds = short.Parse(token[1]) * 6;
var hms = $"{token[0]}:{seconds}";
return _utilities.HMSToHours(hms);
}
public double RightAscension
{
get
{
CheckConnected("RightAscension Get");
var result = SharedResourcesWrapper.SendString(":GR#");
//:GR# Get Telescope RA
//Returns: HH:MM.T# or HH:MM:SS#
//Depending which precision is set for the telescope
double rightAscension = HmToHours(result);
LogMessage("RightAscension", $"Get - {result} convert to {rightAscension} {_utilitiesExtra.HoursToHMS(rightAscension)}");
return rightAscension;
}
}
public double RightAscensionRate
{
get
{
double rightAscensionRate = 0.0;
LogMessage("RightAscensionRate", "Get - " + rightAscensionRate.ToString(CultureInfo.InvariantCulture));
return rightAscensionRate;
}
// ReSharper disable once ValueParameterNotUsed
set
{
LogMessage("RightAscensionRate Set", "Not implemented");
throw new PropertyNotImplementedException("RightAscensionRate", true);
}
}
public void SetPark()
{
LogMessage("SetPark", "Not implemented");
throw new MethodNotImplementedException("SetPark");
}
public PierSide SideOfPier
{
get
{
LogMessage("SideOfPier Get", "Not implemented");
throw new PropertyNotImplementedException("SideOfPier", false);
}
// ReSharper disable once ValueParameterNotUsed
set
{
LogMessage("SideOfPier Set", "Not implemented");
throw new PropertyNotImplementedException("SideOfPier", true);
}
}
public double SiderealTime
{
get
{
// Now using NOVAS 3.1
double siderealTime = 0.0;
using (var novas = new NOVAS31())
{
var jd = _utilities.DateUTCToJulian(DateTime.UtcNow);
novas.SiderealTime(jd, 0, novas.DeltaT(jd),
GstType.GreenwichApparentSiderealTime,
Method.EquinoxBased,
Accuracy.Reduced, ref siderealTime);
}
// Allow for the longitude
siderealTime += SiteLongitude / 360.0 * 24.0;
// Reduce to the range 0 to 24 hours
siderealTime = _astroUtilities.ConditionRA(siderealTime);
LogMessage("SiderealTime", "Get - " + siderealTime.ToString(CultureInfo.InvariantCulture));
return siderealTime;
}
}
public new double SiteElevation
{
get
{
CheckConnected("SiteElevation Get");
LogMessage("SiteElevation", $"Get {base.SiteElevation}");
return base.SiteElevation;
}
// ReSharper disable once ValueParameterNotUsed
set
{
CheckConnected("SiteElevation Set");
LogMessage("SiteElevation", $"Set: {value}");
if (Math.Abs(value - base.SiteElevation) < 0.1)
{
LogMessage("SiteElevation", $"Set: no change detected");
return;
}
LogMessage("SiteElevation", $"Set: {value} was {base.SiteElevation}");
base.SiteElevation = value;
base.UpdateSiteElevation();
}
}
public double SiteLatitude
{
get
{
CheckConnected("SiteLatitude Get");
var latitude = SharedResourcesWrapper.SendString(":Gt#");
//:Gt# Get Current Site Latitude
//Returns: sDD* MM#
//The latitude of the current site. Positive inplies North latitude.
var siteLatitude = _utilities.DMSToDegrees(latitude);
LogMessage("SiteLatitude Get", $"{_utilitiesExtra.DegreesToDMS(siteLatitude)}");
return siteLatitude;
}
set
{
LogMessage("SiteLatitude Set", $"{_utilitiesExtra.DegreesToDMS(value)}");
CheckConnected("SiteLatitude Set");
if (value > 90)
throw new InvalidValueException("Latitude cannot be greater than 90 degrees.");
if (value < -90)
throw new InvalidValueException("Latitude cannot be less than -90 degrees.");
string sign = value > 0 ? "+" : "-";
var absValue = Math.Abs(value);
int d = Convert.ToInt32(Math.Floor(absValue));
int m = Convert.ToInt32(60 * (absValue - d));
var commandString = $":St{sign}{d:00}*{m:00}#";
var result = SharedResourcesWrapper.SendChar(commandString);
//:StsDD*MM#
//Sets the current site latitude to sDD* MM#
//Returns:
//0 Invalid
//1 - Valid
if (result != "1")
throw new InvalidOperationException("Failed to set site latitude.");
}
}
public double SiteLongitude
{
get
{
CheckConnected("SiteLongitude Get");
var longitude = SharedResourcesWrapper.SendString(":Gg#");
//:Gg# Get Current Site Longitude
//Returns: sDDD*MM#
//The current site Longitude. East Longitudes are expressed as negative
double siteLongitude = -_utilities.DMSToDegrees(longitude);
if (siteLongitude < -180)
siteLongitude = siteLongitude + 360;
LogMessage("SiteLongitude Get", $"{_utilitiesExtra.DegreesToDMS(siteLongitude)}");
return siteLongitude;
}
set
{
var newLongitude = value;
LogMessage("SiteLongitude Set", $"{_utilitiesExtra.DegreesToDMS(newLongitude)}");
CheckConnected("SiteLongitude Set");
if (newLongitude > 180)
throw new InvalidValueException("Longitude cannot be greater than 180 degrees.");
if (newLongitude < -180)
throw new InvalidValueException("Longitude cannot be lower than -180 degrees.");
if (newLongitude > 0)
newLongitude = 360 - newLongitude;
newLongitude = Math.Abs(newLongitude);
int d = Convert.ToInt32(Math.Floor(newLongitude));
int m = Convert.ToInt32(60 * (newLongitude - d));
var commandstring = $":Sg{d:000}*{m:00}#";
var result = SharedResourcesWrapper.SendChar(commandstring);
//:SgDDD*MM#
//Set current sites longitude to DDD*MM an ASCII position string
//Returns:
//0 Invalid
//1 - Valid
if (result != "1")
throw new InvalidOperationException("Failed to set site longitude.");
}
}
public short SlewSettleTime
{
get
{
CheckConnected("SlewSettleTime Get");
LogMessage("SlewSettleTime Get", $"{_settleTime} Seconds");
return _settleTime;
}
set
{
CheckConnected("SlewSettleTime Set");
LogMessage("SlewSettleTime Set", $"Setting from {_settleTime} to {value}");
_settleTime = value;
}
}
public void SlewToAltAz(double azimuth, double altitude)
{
LogMessage("SlewToAltAz", $"Az=~{azimuth} Alt={altitude}");
CheckConnected("SlewToAltAz");
SlewToAltAzAsync(azimuth, altitude);
while (Slewing) //wait for slew to complete
{
_utilities.WaitForMilliseconds(200); //be responsive to AbortSlew();
}
}
public void SlewToAltAzAsync(double azimuth, double altitude)
{
CheckConnected("SlewToAltAzAsync");
if (altitude > 90)
throw new InvalidValueException("Altitude cannot be greater than 90.");
if (altitude < 0)
throw new InvalidValueException("Altitide cannot be less than 0.");
if (azimuth >= 360)
throw new InvalidValueException("Azimuth cannot be 360 or higher.");
if (azimuth < 0)
throw new InvalidValueException("Azimuth cannot be less than 0.");
LogMessage("SlewToAltAzAsync", $"Az={azimuth} Alt={altitude}");
HorizonCoordinates altAz = new HorizonCoordinates {Azimuth = azimuth, Altitude = altitude};
var utcDateTime = UTCDate;
var latitude = SiteLatitude;
var longitude = SiteLongitude;
SharedResourcesWrapper.Lock(() =>
{
var raDec = _astroMaths.ConvertHozToEq(utcDateTime, latitude, longitude, altAz);
TargetRightAscension = raDec.RightAscension;
TargetDeclination = raDec.Declination;
DoSlewAsync(true);
//TargetAltitude = altitude;
//TargetAzimuth = azimuth;
//DoSlewAsync(false);
});
}
private void DoSlewAsync(bool polar)
{
CheckConnected("DoSlewAsync");
SharedResourcesWrapper.Lock(() =>
{
switch (polar)
{
case true:
var response = SharedResourcesWrapper.SendChar(":MS#");
//:MS# Slew to Target Object
//Returns:
//0 Slew is Possible
//1<string># Object Below Horizon w/string message
//2<string># Object Below Higher w/string message
switch (response)
{
case "0":
//We're slewing everything should be working just fine.
LogMessage("DoSlewAsync", "Slewing to target");
SetSlewingMinEndTime();
break;
case "1":
//Below Horizon
string belowHorizonMessage = SharedResourcesWrapper.ReadTerminated();
LogMessage("DoSlewAsync", $"Slew failed \"{belowHorizonMessage}\"");
throw new InvalidOperationException(belowHorizonMessage);
case "2":
//Below minimum elevation
string belowMinimumElevationMessage = SharedResourcesWrapper.ReadTerminated();
LogMessage("DoSlewAsync", $"Slew failed \"{belowMinimumElevationMessage}\"");
throw new InvalidOperationException(belowMinimumElevationMessage);
case "3":
//Telescope can hit the mount
string canHitMountMessage = SharedResourcesWrapper.ReadTerminated();
LogMessage("DoSlewAsync", $"Slew failed \"{canHitMountMessage}\"");
throw new InvalidOperationException(canHitMountMessage);
default:
LogMessage("DoSlewAsync", $"Slew failed - unknown response \"{response}\"");
throw new DriverException("This error should not happen");
}
break;
case false:
var maResponse = SharedResourcesWrapper.SendChar(":MA#");
//:MA# Autostar, LX 16, Autostar II Slew to target Alt and Az
//Returns:
//0 - No fault
//1 Fault
// LX200 Not supported
if (maResponse == "1")
{
throw new InvalidOperationException("fault");
}
SetSlewingMinEndTime();
break;
}
});
}
public void SlewToCoordinates(double rightAscension, double declination)
{
LogMessage("SlewToCoordinates", $"Ra={rightAscension}, Dec={declination}");
CheckConnected("SlewToCoordinates");
SlewToCoordinatesAsync(rightAscension, declination);
while (Slewing) //wait for slew to complete
{
_utilities.WaitForMilliseconds(200); //be responsive to AbortSlew();
}
LogMessage("SlewToCoordinates", $"Slewing completed new coordinates Ra={RightAscension}, Dec={Declination}");
}
public void SlewToCoordinatesAsync(double rightAscension, double declination)
{
LogMessage("SlewToCoordinatesAsync", $"Ra={rightAscension}, Dec={declination}");
CheckConnected("SlewToCoordinatesAsync");
SharedResourcesWrapper.Lock(() =>
{
TargetRightAscension = rightAscension;
TargetDeclination = declination;
DoSlewAsync(true);
}
);
}
public void SlewToTarget()
{
LogMessage("SlewToTarget", "Executing");
CheckConnected("SlewToTarget");
SlewToTargetAsync();
while (Slewing)
{
_utilities.WaitForMilliseconds(200);
}
}
private const double InvalidParameter = -1000;
public void SlewToTargetAsync()
{
CheckConnected("SlewToTargetAsync");
if (TargetDeclination.Equals(InvalidParameter) || TargetRightAscension.Equals(InvalidParameter))
throw new InvalidOperationException("No target selected to slew to.");
DoSlewAsync(true);
}
private bool MovingAxis()
{
if (_isGuiding)
return false;
return _movingPrimary || _movingSecondary;
}
private DateTime _earliestNonSlewingTime = DateTime.MinValue;
public bool Slewing
{
get
{
var isSlewing = GetSlewing();
if (isSlewing)
SetSlewingMinEndTime();
else if (_clock.UtcNow < _earliestNonSlewingTime)
isSlewing = true;
LogMessage("Slewing", $"Result = {isSlewing}");
return isSlewing;
}
}
private void SetSlewingMinEndTime()
{
_earliestNonSlewingTime = _clock.UtcNow + GetTotalSlewingSettleTime();
}
private TimeSpan GetTotalSlewingSettleTime()
{
return TimeSpan.FromSeconds( SlewSettleTime + ProfileSettleTime );
}
private bool GetSlewing()
{
if (!Connected) return false;
if (MovingAxis())
return true;
return IsSlewingToTarget();
}
private bool IsSlewingToTarget()
{
CheckConnected("IsSlewingToTarget");
if (_isGuiding)
return false;
var result = SharedResourcesWrapper.SendString(":D#");
//:D# Requests a string of bars indicating the distance to the current target location.
//Returns:
//LX200's a string of bar characters indicating the distance.
//Autostars and Autostar II a string containing one bar until a slew is complete, then a null string is returned.
bool isSlewing = false;
try
{
if (string.IsNullOrEmpty(result))
{
// ReSharper disable once RedundantAssignment
isSlewing = false;
return isSlewing;
}
if (result.Contains("|"))
{
isSlewing = true;
return isSlewing;
}
////classic LX200 return bar with 32 chars. FF is contained from left to right when slewing
//byte[] ba = Encoding.Default.GetBytes(result);
////replace fill chars not belonging to a slew bar. Are there others? The bar character is a FF in hex.
//var hexString = BitConverter.ToString(ba).Replace("-", "").Replace("20", "");
//LogMessage("IsSlewingToTarget", $"Resulthex = {hexString}");
//isSlewing = (hexString.Length > 0);
//if (!isSlewing)
// return isSlewing;
////classic LX200 got RA 0 DE 0 as Target Coordinates. If the RA DE is not 0 at switch on, the telescope will indicate slewing until
////the target coordinates are set and the telescope is slewed to that position.
////a 0 movement will solved that lock if the target coordinates are set to the current coordinates.
//if (IsTargetCoordinateInitRequired())
// InitTargetCoordinates();
return isSlewing;
}
finally
{
LogMessage("IsSlewingToTarget", $"IsSlewing = {isSlewing} : result = {result ?? "<null>"}");
}
}
public void SyncToAltAz(double azimuth, double altitude)
{
LogMessage("SyncToAltAz", "Not implemented");
throw new MethodNotImplementedException("SyncToAltAz");
}
public void SyncToCoordinates(double rightAscension, double declination)
{
LogMessage("SyncToCoordinates", $"RA={rightAscension} Dec={declination}");
LogMessage("SyncToCoordinates", $"RA={_utilitiesExtra.HoursToHMS(rightAscension)} Dec={_utilitiesExtra.HoursToHMS(declination)}");
CheckConnected("SyncToCoordinates");
SharedResourcesWrapper.Lock(() =>
{
TargetRightAscension = rightAscension;
TargetDeclination = declination;
SyncToTarget();
});
}
public void SyncToTarget()
{
LogMessage("SyncToTarget", "Executing");
CheckConnected("SyncToTarget");
var result = SharedResourcesWrapper.SendString(":CM#");
//:CM# Synchronizes the telescope's position with the currently selected database object's coordinates.
//Returns:
//LX200's - a "#" terminated string with the name of the object that was synced.
// Autostars & Autostar II - A static string: " M31 EX GAL MAG 3.5 SZ178.0'#"
if (string.IsNullOrWhiteSpace(result))
throw new InvalidOperationException("Unable to perform sync");
// At least the classic LX200 low precision might not slew to the exact target position
// This Requires to retrieve the aimed target ra de from the telescope
double ra = RightAscension;
if (Math.Abs(_targetRightAscension - InvalidParameter) > 0.1 &&
_utilities.HoursToHMS(ra, ":", ":", ":", _digitsRa) != _utilities.HoursToHMS(_targetRightAscension, ":", ":", ":", _digitsRa))
{
LogMessage("SyncToTarget", $"differ RA real {ra} targeted {_targetRightAscension}");
_targetRightAscension = ra;
}
double de = Declination;
if (Math.Abs(_targetDeclination - InvalidParameter) > 0.1 &&
_utilities.DegreesToDMS(de, "*", ":", ":", _digitsDe) != _utilities.DegreesToDMS(_targetDeclination, "*", ":", ":", _digitsDe))
{
LogMessage("SyncToTarget", $"differ DE real {de} targeted {_targetDeclination}");
_targetDeclination = de;
}
}
private double _targetDeclination = InvalidParameter;
public double TargetDeclination
{
get
{
if (_targetDeclination.Equals(InvalidParameter))
throw new InvalidOperationException("Target not set");
//var result = SerialPort.CommandTerminated(":Gd#", "#");
////:Gd# Get Currently Selected Object/Target Declination
////Returns: sDD* MM# or sDD*MMSS#
////Depending upon the current precision setting for the telescope.
//double targetDec = DmsToDouble(result);
//return targetDec;
LogMessage("TargetDeclination Get", $"{_targetDeclination}");
return _targetDeclination;
}
set
{
LogMessage("TargetDeclination Set", $"{value}");
CheckConnected("TargetDeclination Set");
if (value > 90)
throw new InvalidValueException("Declination cannot be greater than 90.");
if (value < -90)
throw new InvalidValueException("Declination cannot be less than -90.");
var dms = IsLongFormat ?
_utilities.DegreesToDMS(value, "*", ":", ":", _digitsDe) :
_utilities.DegreesToDM(value, "*", "", _digitsDe);
var s = value < 0 ? string.Empty : "+";
var command = $":Sd{s}{dms}#";
LogMessage("TargetDeclination Set", $"{command}");
var result = SharedResourcesWrapper.SendChar(command);
//:SdsDD*MM#
//Set target object declination to sDD*MM or sDD*MM:SS depending on the current precision setting
//Returns:
//1 - Dec Accepted
//0 Dec invalid
if (result == "0")
{
throw new InvalidOperationException("Target declination invalid");
}
_targetDeclination = _utilities.DMSToDegrees(dms);
}
}
private double _targetRightAscension = InvalidParameter;
public double TargetRightAscension
{
get
{
if (_targetRightAscension.Equals(InvalidParameter))
throw new InvalidOperationException("Target not set");
//var result = SerialPort.CommandTerminated(":Gr#", "#");
////:Gr# Get current/target object RA
////Returns: HH: MM.T# or HH:MM:SS
////Depending upon which precision is set for the telescope
//double targetRa = HmsToDouble(result);
//return targetRa;
LogMessage("TargetRightAscension Get", $"{_targetRightAscension}");
return _targetRightAscension;
}
set
{
LogMessage("TargetRightAscension Set", $"{value}");
CheckConnected("TargetRightAscension Set");
if (value < 0)
throw new InvalidValueException("Right ascension value cannot be below 0");
if (value >= 24)
throw new InvalidValueException("Right ascension value cannot be greater than 23:59:59");
var hms = IsLongFormat ?
_utilities.HoursToHMS(value, ":", ":", ":", _digitsRa) :
_utilities.HoursToHM(value, ":", "", _digitsRa).Replace(',','.');
var command = $":Sr{hms}#";
LogMessage("TargetRightAscension Set", $"{command}");
var response = SharedResourcesWrapper.SendChar(command);
//:SrHH:MM.T#
//:SrHH:MM:SS#
//Set target object RA to HH:MM.T or HH: MM: SS depending on the current precision setting.
// Returns:
//0 Invalid
//1 - Valid
if (response == "0")
throw new InvalidOperationException("Failed to set TargetRightAscension.");
_targetRightAscension = _utilities.HMSToHours(hms);
}
}
private bool _tracking = true;
public bool Tracking
{
get
{
LogMessage("Tracking", $"Get - {_tracking}");
if (FirmwareIsGreaterThan(TelescopeList.Autostar497_43Eg))
{
var alignmentStatus = GetScopeAlignmentStatus();
_tracking = alignmentStatus.Tracking;
}
return _tracking;
}
set
{
throw new ASCOM.NotImplementedException("Tracking Set");
//LogMessage("Tracking Set", $"{value}");
//_tracking = value;
}
}
private DriveRates _trackingRate = DriveRates.driveSidereal;
public DriveRates TrackingRate
{
get
{
//todo implement this with the GW command
//var result = SerialPort.CommandTerminated(":GT#", "#");
//double rate = double.Parse(result);
//if (rate == 60.1)
// return DriveRates.driveLunar;
//else if (rate == 60.1)
// return DriveRates.driveSidereal;
//return DriveRates.driveKing;
LogMessage("TrackingRate Get", $"{_trackingRate}");
return _trackingRate;
}
set
{
LogMessage("TrackingRate Set", $"{value}");
CheckConnected("TrackingRate Set");
switch (value)
{
case DriveRates.driveSidereal:
SharedResourcesWrapper.SendBlind(":TQ#");
//:TQ# Selects sidereal tracking rate
//Returns: Nothing
break;
case DriveRates.driveLunar:
SharedResourcesWrapper.SendBlind(":TL#");
//:TL# Set Lunar Tracking Rage
//Returns: Nothing
break;
//case DriveRates.driveSolar:
// SerialPort.Command(":TS#");
// //:TS# Select Solar tracking rate. [LS Only]
// //Returns: Nothing
// break;
//case DriveRates.driveKing:
//:TM# Select custom tracking rate [ no-op in Autostar II]
//Returns: Nothing
// break;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
_trackingRate = value;
}
}
public ITrackingRates TrackingRates
{
get
{
ITrackingRates trackingRates = new TrackingRates();
LogMessage("TrackingRates", "Get - ");
foreach (DriveRates driveRate in trackingRates)
{
LogMessage("TrackingRates", "Get - " + driveRate);
}
return trackingRates;
}
}
private TimeSpan GetUtcCorrection()
{
string utcOffSet = SharedResourcesWrapper.SendString(":GG#");
//:GG# Get UTC offset time
//Returns: sHH# or sHH.H#
//The number of decimal hours to add to local time to convert it to UTC. If the number is a whole number the
//sHH# form is returned, otherwise the longer form is returned.
double utcOffsetHours = double.Parse(utcOffSet);
TimeSpan utcCorrection = TimeSpan.FromHours(utcOffsetHours);
return utcCorrection;
}
public class TelescopeDateDetails
{
public string TelescopeDate { get; set; }
public string TelescopeTime { get; set; }
public TimeSpan UtcCorrection { get; set; }
}
public DateTime UTCDate
{
get
{
CheckConnected("UTCDate Get");
LogMessage("UTCDate", "Get started");
var telescopeDateDetails = SharedResourcesWrapper.Lock(() =>
{
var tdd = new TelescopeDateDetails
{
TelescopeDate = SharedResourcesWrapper.SendString(":GC#"),
//:GC# Get current date.
//Returns: MM/DD/YY#
//The current local calendar date for the telescope.
TelescopeTime = SharedResourcesWrapper.SendString(":GL#"),
//:GL# Get Local Time in 24 hour format
//Returns: HH:MM:SS#
//The Local Time in 24 - hour Format
UtcCorrection = GetUtcCorrection()
};
return tdd;
});
int month = telescopeDateDetails.TelescopeDate.Substring(0, 2).ToInteger();
int day = telescopeDateDetails.TelescopeDate.Substring(3, 2).ToInteger();
int year = telescopeDateDetails.TelescopeDate.Substring(6, 2).ToInteger();
if (year < 2000) //todo fix this hack that will create a Y2K100 bug
{
year = year + 2000;
}
int hour = telescopeDateDetails.TelescopeTime.Substring(0, 2).ToInteger();
int minute = telescopeDateDetails.TelescopeTime.Substring(3, 2).ToInteger();
int second = telescopeDateDetails.TelescopeTime.Substring(6, 2).ToInteger();
var utcDate = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc) +
telescopeDateDetails.UtcCorrection;
LogMessage("UTCDate", "Get - " + utcDate.ToString("MM/dd/yy HH:mm:ss"));
return utcDate;
}
set
{
LogMessage("UTCDate", "Set - " + value.ToString("MM/dd/yy HH:mm:ss"));
CheckConnected("UTCDate Set");
SharedResourcesWrapper.Lock(() =>
{
var utcCorrection = GetUtcCorrection();
var localDateTime = value - utcCorrection;
string localStingCommand = $":SL{localDateTime:HH:mm:ss}#";
var timeResult = SharedResourcesWrapper.SendChar(localStingCommand);
//:SLHH:MM:SS#
//Set the local Time
//Returns:
//0 Invalid
//1 - Valid
if (timeResult != "1")
{
throw new InvalidOperationException("Failed to set local time");
}
string localDateCommand = $":SC{localDateTime:MM/dd/yy}#";
var dateResult = SharedResourcesWrapper.SendChar(localDateCommand);
//:SCMM/DD/YY#
//Change Handbox Date to MM/DD/YY
//Returns: <D><string>
//D = 0 if the date is invalid.The string is the null string.
//D = 1 for valid dates and the string is Updating Planetary Data# #
//Note: For Autostar II this is the UTC data!
if (dateResult != "1")
{
throw new InvalidOperationException("Failed to set local date");
}
//throwing away these two strings which represent
SharedResourcesWrapper.ReadTerminated(); //Updating Planetary Data#
SharedResourcesWrapper.ReadTerminated(); // #
});
}
}
public void Unpark()
{
LogMessage("Unpark", "Unparking telescope");
//todo make this return only work for LX-200 GPS telescopes
if (!AtPark)
return;
SharedResourcesWrapper.SendChar(":I#");
//:I# LX200 GPS Only - Causes the telescope to cease current operations and restart at its power on initialization.
//Returns: X once the handset restart has completed
var utcCorrection = GetUtcCorrection();
var localDateTime = DateTime.UtcNow - utcCorrection;
//localDateTime: HH: mm: ss
SharedResourcesWrapper.SendBlind($":hI{localDateTime:yyMMddhhmmss}#");
//:hIYYMMDDHHMMSS#
//Bypass handbox entry of daylight savings, date and time.Use the values supplied in this command.This feature is
//intended to allow use of the Autostar II from permanent installations where GPS reception is not possible, such as within
//metal domes. This command must be issued while the telescope is waiting at the initial daylight savings prompt.
//Returns: 1 if command was accepted.
AtPark = false;
}
#endregion
#region Private properties and methods
// here are some useful properties and methods that can be used as required
// to help with driver development
#region ASCOM Registration
// Register or unregister driver for ASCOM. This is harmless if already
// registered or unregistered.
//
/// <summary>
/// Register or unregister the driver with the ASCOM Platform.
/// This is harmless if the driver is already registered/unregistered.
/// </summary>
/// <param name="bRegister">If <c>true</c>, registers the driver, otherwise unregisters it.</param>
private static void RegUnregAscom(bool bRegister)
{
using (IProfileWrapper p = ProfileFactory.Create())
{
p.DeviceType = "Telescope";
if (bRegister)
{
p.Register(DriverId, DriverDescription);
}
else
{
p.Unregister(DriverId);
}
}
}
/// <summary>
/// This function registers the driver with the ASCOM Chooser and
/// is called automatically whenever this class is registered for COM Interop.
/// </summary>
/// <param name="t">Type of the class being registered, not used.</param>
/// <remarks>
/// This method typically runs in two distinct situations:
/// <list type="numbered">
/// <item>
/// In Visual Studio, when the project is successfully built.
/// For this to work correctly, the option <c>Register for COM Interop</c>
/// must be enabled in the project settings.
/// </item>
/// <item>During setup, when the installer registers the assembly for COM Interop.</item>
/// </list>
/// This technique should mean that it is never necessary to manually register a driver with ASCOM.
/// </remarks>
[ComRegisterFunction]
public static void RegisterAscom(Type t)
{
RegUnregAscom(true);
}
/// <summary>
/// This function unregisters the driver from the ASCOM Chooser and
/// is called automatically whenever this class is unregistered from COM Interop.
/// </summary>
/// <param name="t">Type of the class being registered, not used.</param>
/// <remarks>
/// This method typically runs in two distinct situations:
/// <list type="numbered">
/// <item>
/// In Visual Studio, when the project is cleaned or prior to rebuilding.
/// For this to work correctly, the option <c>Register for COM Interop</c>
/// must be enabled in the project settings.
/// </item>
/// <item>During uninstall, when the installer unregisters the assembly from COM Interop.</item>
/// </list>
/// This technique should mean that it is never necessary to manually unregister a driver from ASCOM.
/// </remarks>
[ComUnregisterFunction]
public static void UnregisterAscom(Type t)
{
RegUnregAscom(false);
}
#endregion
/// <summary>
/// Use this function to throw an exception if we aren't connected to the hardware
/// </summary>
/// <param name="message"></param>
private void CheckConnected(string message)
{
if (!IsConnected)
{
throw new NotConnectedException($"Not connected to telescope when trying to execute: {message}");
}
}
private void WriteProfile()
{
var profileProperties = new ProfileProperties
{
TraceLogger = Tl.Enabled,
ComPort = ComPort,
GuideRateArcSecondsPerSecond = GuideRate
};
SharedResourcesWrapper.WriteProfile(profileProperties);
}
#endregion
}
}