#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. // /// /// ASCOM Telescope Driver for Meade.net. /// [Guid("d9fd4b3e-c4f1-48ac-a16f-d02eef30d86f")] [ProgId("ASCOM.MeadeGeneric.Telescope")] [ServedClassName("Meade Generic")] [ClassInterface(ClassInterfaceType.None)] [ComVisible(true)] public class Telescope : ReferenceCountedObjectBase, ITelescopeV3 { /// /// ASCOM DeviceID (COM ProgID) for this driver. /// The DeviceID is used by ASCOM applications to load the driver at runtime. /// //internal static string driverID = "ASCOM.Meade.net.Telescope"; private static readonly string DriverId = Marshal.GenerateProgIdForType(MethodBase.GetCurrentMethod().DeclaringType ?? throw new System.InvalidOperationException()); /// /// Driver description that displays in the ASCOM Chooser. /// private static readonly string DriverDescription = "Meade Generic"; private static string _comPort; // Variables to hold the currrent device configuration /// /// Private variable to hold an ASCOM Utilities object /// private readonly IUtil _utilities; private readonly IUtilExtra _utilitiesExtra; /// /// Private variable to hold an ASCOM AstroUtilities object to provide the Range method /// private readonly IAstroUtils _astroUtilities; private readonly IAstroMaths _astroMaths; /// /// Variable to hold the trace logger object (creates a diagnostic log file with information that you specify) /// private TraceLogger _tl; private readonly ISharedResourcesWrapper _sharedResourcesWrapper; /// /// Initializes a new instance of the class. /// Must be public for COM registration. /// 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 _sharedResourcesWrapper = new SharedResourcesWrapper(); _astroMaths = new AstroMaths.AstroMaths(); Initialise(); } 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) { _utilities = util; //Initialise util object _utilitiesExtra = utilExtra; //Initialise util object _astroUtilities = astroUtilities; // Initialise astro utilities object _sharedResourcesWrapper = sharedResourcesWrapper; _astroMaths = astroMaths; Initialise(); } private double _guideRate; private bool _isGuiding; private void Initialise() { //todo move the TraceLogger out to a factory class. _tl = new TraceLogger("", "Meade.Generic.Telescope"); ReadProfile(); // Read device configuration from the ASCOM Profile store IsConnected = false; // Initialise connected to false LogMessage("Telescope", "Completed initialisation"); LogMessage("Telescope", $"Driver version: {DriverVersion}"); } // // PUBLIC COM INTERFACE ITelescopeV3 IMPLEMENTATION // #region Common properties and methods. /// /// 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! /// 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; } public void SetLongFormat(bool setLongFormat) { IsLongFormat = false; if (!IsLongFormatSupported()) { LogMessage("SetLongFormat", "Long coordinate format not supported for this mount"); return; } _sharedResourcesWrapper.Lock(() => { var result = _sharedResourcesWrapper.SendString(":GZ#"); //:GZ# Get telescope azimuth //Returns: DDD*MM# or DDD*MM’SS# //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 } }); 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: //“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# //Set current site to, 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# //Set site 1’s name to be.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# //Set site 2’s name to be.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# //Set site 3’s name to be.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# //Set site 4’s name to be.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: # //A ‘#’ terminated string with the name of the requested site. case 2: return _sharedResourcesWrapper.SendString(":GN#"); //:GN# Get Site 2 Name //Returns: # //A ‘#’ terminated string with the name of the requested site. case 3: return _sharedResourcesWrapper.SendString(":GO#"); //:GO# Get Site 3 Name //Returns: # //A ‘#’ terminated string with the name of the requested site. case 4: return _sharedResourcesWrapper.SendString(":GP#"); //:GP# Get Site 4 Name //Returns: # //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 string Description { get { LogMessage("Description Get", DriverDescription); return DriverDescription; } } public string DriverInfo { get { string driverInfo = $"{Description} .net driver. Version: {DriverVersion}"; LogMessage("DriverInfo Get", driverInfo); return driverInfo; } } public string DriverVersion { get { Version version = Assembly.GetExecutingAssembly().GetName().Version; string driverVersion = $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}"; LogMessage("DriverVersion Get", driverVersion); return driverVersion; } } 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: # //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; } public AlignmentModes AlignmentMode { get { LogMessage("AlignmentMode Get", "Getting alignmode"); CheckConnected("AlignmentMode Get"); const char ack = (char) 6; var alignmentString = _sharedResourcesWrapper.SendChar(ack.ToString()); //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 //todo implement GW Command - Supported in Autostar 43Eg and above //if FirmwareIsGreaterThan(TelescopeList.Autostar497_43EG) //{ //var alignmentString = SerialPort.CommandTerminated(":GW#", "#"); //:GW# Get Scope Alignment Status //Returns: # // 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. //} 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 } } 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*MM’SS# ////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*MM’SS# //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 - " + true); return true; } } 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 { LogMessage("CanUnpark", "Get - " + false); return false; } } public double Declination { get { CheckConnected("Declination Get"); var result = _sharedResourcesWrapper.SendString(":GD#"); //:GD# Get Telescope Declination. //Returns: sDD*MM# or sDD*MM’SS# //Depending upon the current precision setting for the telescope. double declination = _utilities.DMSToDegrees(result); LogMessage("Declination", "Get - " + _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: _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: _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 16”Slew 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; } } 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 = _utilities.HMSToHours(result); LogMessage("RightAscension", "Get - " + _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 double SiteElevation { get { LogMessage("SiteElevation Get", "Not implemented"); throw new PropertyNotImplementedException("SiteElevation", false); } // ReSharper disable once ValueParameterNotUsed set { LogMessage("SiteElevation Set", "Not implemented"); throw new PropertyNotImplementedException("SiteElevation", true); } } 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; siteLongitude = -siteLongitude; 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 site’s 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 { LogMessage("SlewSettleTime Get", "Not implemented"); throw new PropertyNotImplementedException("SlewSettleTime", false); } // ReSharper disable once ValueParameterNotUsed set { LogMessage("SlewSettleTime Set", "Not implemented"); throw new PropertyNotImplementedException("SlewSettleTime", true); } } 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# Object Below Horizon w/string message //2# Object Below Higher w/string message switch (response) { case "0": //We're slewing everything should be working just fine. LogMessage("DoSlewAsync", "Slewing to target"); 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"); } 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; } public bool Slewing { get { if (!Connected) return false; if (MovingAxis()) return true; return IsSlewingToTarget(); } } private bool IsSlewingToTarget() { CheckConnected("Slewing Get"); 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. if (result == null) { return false; } bool isSlewing = result.Trim() != string.Empty; LogMessage("Slewing Get", $"Result = {isSlewing}"); return isSlewing; } 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}"); 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 (result == string.Empty) throw new InvalidOperationException("Unable to perform sync"); } 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*MM’SS# ////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"); //todo implement low precision version of this. 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 = _utilities.DegreesToDMS(value, "*", ":", ":", 2); 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 = value; } } 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"); //todo implement the low precision version var hms = _utilities.HoursToHMS(value, ":", ":", ":", 2); var response = _sharedResourcesWrapper.SendChar($":Sr{hms}#"); //: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 = value; } } private bool _tracking = true; public bool Tracking { get { LogMessage("Tracking", $"Get - {_tracking}"); return _tracking; } set { LogMessage("Tracking Set", $"{value}"); _tracking = value; } } private DriveRates _trackingRate = DriveRates.driveSidereal; private string _precision; private string _guidingStyle; 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 = ‘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() { //todo heard a rumour that it's possible to unpark an LX200GPS. LogMessage("Unpark", "Not implemented"); throw new MethodNotImplementedException("Unpark"); } #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 private static IProfileFactory _profileFactory; public static IProfileFactory ProfileFactory { get => _profileFactory ?? (_profileFactory = new ProfileFactory()); set => _profileFactory = value; } // Register or unregister driver for ASCOM. This is harmless if already // registered or unregistered. // /// /// Register or unregister the driver with the ASCOM Platform. /// This is harmless if the driver is already registered/unregistered. /// /// If true, registers the driver, otherwise unregisters it. private static void RegUnregAscom(bool bRegister) { using (IProfileWrapper p = ProfileFactory.Create()) { p.DeviceType = "Telescope"; if (bRegister) { p.Register(DriverId, DriverDescription); } else { p.Unregister(DriverId); } } } /// /// This function registers the driver with the ASCOM Chooser and /// is called automatically whenever this class is registered for COM Interop. /// /// Type of the class being registered, not used. /// /// This method typically runs in two distinct situations: /// /// /// In Visual Studio, when the project is successfully built. /// For this to work correctly, the option Register for COM Interop /// must be enabled in the project settings. /// /// During setup, when the installer registers the assembly for COM Interop. /// /// This technique should mean that it is never necessary to manually register a driver with ASCOM. /// [ComRegisterFunction] public static void RegisterAscom(Type t) { RegUnregAscom(true); } /// /// This function unregisters the driver from the ASCOM Chooser and /// is called automatically whenever this class is unregistered from COM Interop. /// /// Type of the class being registered, not used. /// /// This method typically runs in two distinct situations: /// /// /// In Visual Studio, when the project is cleaned or prior to rebuilding. /// For this to work correctly, the option Register for COM Interop /// must be enabled in the project settings. /// /// During uninstall, when the installer unregisters the assembly from COM Interop. /// /// This technique should mean that it is never necessary to manually unregister a driver from ASCOM. /// [ComUnregisterFunction] public static void UnregisterAscom(Type t) { RegUnregAscom(false); } #endregion /// /// Returns true if there is a valid connection to the driver hardware /// private bool IsConnected { get; set; } /// /// Use this function to throw an exception if we aren't connected to the hardware /// /// private void CheckConnected(string message) { if (!IsConnected) { throw new NotConnectedException($"Not connected to telescope when trying to execute: {message}"); } } /// /// Read the device configuration from the ASCOM Profile store /// private void ReadProfile() { ProfileProperties profileProperties = _sharedResourcesWrapper.ReadProfile(); _tl.Enabled = profileProperties.TraceLogger; _comPort = profileProperties.ComPort; _guideRate = profileProperties.GuideRateArcSecondsPerSecond; _precision = profileProperties.Precision; _guidingStyle = profileProperties.GuidingStyle.ToLower(); LogMessage("ReadProfile", $"Trace logger enabled: {_tl.Enabled}"); LogMessage("ReadProfile", $"Com Port: {_comPort}"); LogMessage("ReadProfile", $"Guide Rate: {_guideRate}"); LogMessage("ReadProfile", $"Precision: {_precision}"); LogMessage("ReadProfile", $"Guiding Style: {_guidingStyle}"); } private void WriteProfile() { var profileProperties = new ProfileProperties { TraceLogger = _tl.Enabled, ComPort = _comPort, GuideRateArcSecondsPerSecond = _guideRate }; _sharedResourcesWrapper.WriteProfile(profileProperties); } /// /// Log helper function that takes formatted strings and arguments /// /// /// /// private void LogMessage(string identifier, string message, params object[] args) { var msg = string.Format(message, args); _tl.LogMessage(identifier, msg); } #endregion } }