From 65e06f2d6c3ab4917aef87f978df2e2e58463606 Mon Sep 17 00:00:00 2001 From: Sebastian Godelet Date: Sun, 6 Jun 2021 17:31:57 +1000 Subject: [PATCH] Implemented SideOfPier and DestinationSideOfPier For telescopes that automatically perform a meridian flip we can implement the SideOfPier property by updating the current side of pier after a telescope slew (via DestinationSideOfPier) --- .../TelescopeUnitTests.cs | 115 ++++++++++++++++-- Meade.net.Telescope/Telescope.cs | 114 +++++++++++++---- 2 files changed, 192 insertions(+), 37 deletions(-) diff --git a/Meade.net.Telescope.UnitTests/TelescopeUnitTests.cs b/Meade.net.Telescope.UnitTests/TelescopeUnitTests.cs index c111e34..e86fd2c 100644 --- a/Meade.net.Telescope.UnitTests/TelescopeUnitTests.cs +++ b/Meade.net.Telescope.UnitTests/TelescopeUnitTests.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Reflection; using ASCOM; using ASCOM.Astrometry.AstroUtils; +using ASCOM.Astrometry.NOVAS; using ASCOM.DeviceInterface; using ASCOM.Meade.net; using ASCOM.Meade.net.AstroMaths; @@ -42,6 +43,7 @@ namespace Meade.net.Telescope.UnitTests private Mock _sharedResourcesWrapperMock; private Mock _astroMathsMock; private Mock _clockMock; + private Mock _novasMock; private ProfileProperties _profileProperties; private ConnectionInfo _connectionInfo; @@ -98,15 +100,17 @@ namespace Meade.net.Telescope.UnitTests }; _sharedResourcesWrapperMock.Setup(x => x.Connect("Serial", It.IsAny(), It.IsAny())).Returns( () => _connectionInfo ); - + _sharedResourcesWrapperMock.Setup(x => x.ReadProfile()).Returns(_profileProperties); _astroMathsMock = new Mock(); _clockMock = new Mock(); + _novasMock = new Mock(); + _telescope = new ASCOM.Meade.net.Telescope(_utilMock.Object, _utilExtraMock.Object, _astroUtilsMock.Object, - _sharedResourcesWrapperMock.Object, _astroMathsMock.Object, _clockMock.Object); + _sharedResourcesWrapperMock.Object, _astroMathsMock.Object, _clockMock.Object, _novasMock.Object); } private void ConnectTelescope(string productName = TelescopeList.Autostar497, string firmwareVersion = TelescopeList.Autostar497_31Ee) @@ -191,7 +195,7 @@ namespace Meade.net.Telescope.UnitTests _sharedResourcesWrapperMock.Setup(x => x.SendString("ED", false)).Returns(expectedResult); _telescope.Connected = true; - + var actualResult = _telescope.Action("handbox", "readdisplay"); @@ -402,7 +406,7 @@ namespace Meade.net.Telescope.UnitTests _sharedResourcesWrapperMock.Verify(x => x.SendBool(expectedMessage, raw), Times.Once); Assert.That(result, Is.True); } - + [Test] public void CommandString_WhenNotConnected_ThenThrowsNotConnectedException() { @@ -1199,15 +1203,89 @@ namespace Meade.net.Telescope.UnitTests } [Test] - public void DestinationSideOfPier_ThenThrowsException() + public void DestinationSideOfPier_WhenNotConnected_ThenThrowsException() { - var excpetion = Assert.Throws(() => + var excpetion = Assert.Throws(() => { var result = _telescope.DestinationSideOfPier(0, 0); Assert.Fail($"{result} should not have returned"); }); - Assert.That(excpetion.Method, Is.EqualTo("DestinationSideOfPier")); + Assert.That(excpetion.Message, Is.EqualTo("Not connected to telescope when trying to execute: DestinationSideOfPier")); + } + + [TestCase(1, 1, 4, PierSide.pierEast)] + [TestCase(1, -1, 4, PierSide.pierEast)] + [TestCase(4, 1, 1, PierSide.pierWest)] + [TestCase(4, -1, 1, PierSide.pierWest)] + [TestCase(0, 0, 0, PierSide.pierUnknown)] + [TestCase(5, 0, 5, PierSide.pierUnknown)] + [TestCase(23.8, 1, 23.9, PierSide.pierEast)] + [TestCase(23.8, -1, 23.9, PierSide.pierEast)] + [TestCase(23.9, 1, 1, PierSide.pierEast)] + [TestCase(23.9, -1, 1, PierSide.pierEast)] + [TestCase(1, 1, 23.9, PierSide.pierWest)] + [TestCase(1, -1, 23.9, PierSide.pierWest)] + public void DestinationSideOfPier_WhenHASiderealTimeDiffIsNotNull_ThenSideOfPierIsCalculated(double ra, double dec, double siderealTime, PierSide expectedDSOP) + { + // given + // SideralTime uses ConditionRA to normalize to 0..24h, so we use it to mock the property + _astroUtilsMock.Setup(x => x.ConditionRA(It.IsAny())).Returns(siderealTime); + + var ha = siderealTime - ra; + var normalisedHA = ha > 12 ? ha - 24 : ha < -12 ? ha + 24 : ha; + _astroUtilsMock.Setup(x => x.ConditionHA(It.Is(v => v == ha))).Returns(normalisedHA); + + ConnectTelescope(); + + // when + var actualDSOP = _telescope.DestinationSideOfPier(ra, dec); + + // then + Assert.That(siderealTime, Is.InRange(0, 24 + double.Epsilon)); + Assert.That(normalisedHA, Is.InRange(-12, 12 + double.Epsilon)); + Assert.That(actualDSOP, Is.EqualTo(expectedDSOP)); + + _astroUtilsMock.Verify(x => x.ConditionRA(It.IsAny()), Times.Once); + _astroUtilsMock.Verify(x => x.ConditionHA(It.Is(v => v == ha)), Times.Once); + } + + [Test] + public void SiderealTime_Get_WhenNotConnected_ThenThrowsException() + { + // when + var exception = Assert.Throws(() => + { + var result = _telescope.SiderealTime; + Assert.Fail($"{result} should not have returned"); + }); + + // then + Assert.That(exception.Message, Is.EqualTo("Not connected to telescope when trying to execute: SiderealTime Get")); + } + + [Test] + public void SiderealTime_Get_WhenNOVASErrors_ThenThrowsException() + { + // given + double gst = 0.0; + _novasMock + .Setup(x => x.SiderealTime(It.IsAny(), It.IsAny(), It.IsAny(), ASCOM.Astrometry.GstType.GreenwichApparentSiderealTime, ASCOM.Astrometry.Method.EquinoxBased, ASCOM.Astrometry.Accuracy.Reduced, ref gst)) + .Returns(3) + .Verifiable(); + + ConnectTelescope(); + + // when + var exception = Assert.Throws(() => + { + var result = _telescope.SiderealTime; + Assert.Fail($"{result} should not have returned"); + }); + + // then + Assert.That(exception.Message, Is.EqualTo("NOVAS 3.1 SiderealTime returned: 3 in SiderealTime")); + _novasMock.Verify(); } [Test] @@ -1470,7 +1548,7 @@ namespace Meade.net.Telescope.UnitTests Assert.That(_telescope.AtPark, Is.True); - //act + //act _telescope.Park(); //no change from previous state. @@ -1743,8 +1821,11 @@ namespace Meade.net.Telescope.UnitTests } [Test] - public void SideOfPier_Get_ThenThrowsException() + public void SideOfPier_Get_WhenMeridianFlipNotSupported_ThenThrowsException() { + // LX200 classic is a fork mounted scope so it does not support meridian flips + _sharedResourcesWrapperMock.Setup(x => x.ProductName).Returns(TelescopeList.LX200CLASSIC); + var excpetion = Assert.Throws(() => { var result = _telescope.SideOfPier; @@ -2761,6 +2842,9 @@ namespace Meade.net.Telescope.UnitTests [Test] public void SlewToTarget_WhenSlewing_ThenWaitsForTheSlewToComplete() { + // avoid calling SideOfPier because it will call Slewing + _astroUtilsMock.Setup(x => x.ConditionHA(It.IsAny())).Returns(+1); + _sharedResourcesWrapperMock.Setup(x => x.SendChar("MS", false)).Returns("0"); var slewCounter = 0; @@ -2781,6 +2865,7 @@ namespace Meade.net.Telescope.UnitTests _telescope.SlewToTarget(); _utilMock.Verify(x => x.WaitForMilliseconds(It.IsAny()), Times.Exactly(iterations)); + _astroUtilsMock.Verify(x => x.ConditionHA(It.IsAny()), Times.Once); } [Test] @@ -2841,6 +2926,9 @@ namespace Meade.net.Telescope.UnitTests [Test] public void SlewToCoordinates_WhenCalled_ThenSetsTargetAndSlews() { + // avoid calling SideOfPier because it will call Slewing + _astroUtilsMock.Setup(x => x.ConditionHA(It.IsAny())).Returns(+1); + _testProperties.rightAscension = 1; var declination = 2; @@ -2875,6 +2963,7 @@ namespace Meade.net.Telescope.UnitTests _sharedResourcesWrapperMock.Verify(x => x.SendChar("MS", false), Times.Once); _utilMock.Verify(x => x.WaitForMilliseconds(It.IsAny()), Times.Exactly(iterations)); + _astroUtilsMock.Verify(x => x.ConditionHA(It.IsAny()), Times.Once); } [Test] @@ -2963,6 +3052,9 @@ namespace Meade.net.Telescope.UnitTests [Test] public void SlewToAltAz_WhenCalled_ThenSetsTargetAndSlews() { + // avoid calling SideOfPier because it will call Slewing + _astroUtilsMock.Setup(x => x.ConditionHA(It.IsAny())).Returns(+1); + _testProperties.rightAscension = 10.0; _testProperties.declination = 20; var azimuth = 30; @@ -2995,6 +3087,7 @@ namespace Meade.net.Telescope.UnitTests Assert.That(_telescope.TargetDeclination, Is.EqualTo(_testProperties.declination)); _sharedResourcesWrapperMock.Verify(x => x.SendChar("MS", false), Times.Once); _utilMock.Verify(x => x.WaitForMilliseconds(It.IsAny()), Times.Exactly(iterations)); + _astroUtilsMock.Verify(x => x.ConditionHA(It.IsAny()), Times.Once); } [Test] @@ -3021,8 +3114,8 @@ namespace Meade.net.Telescope.UnitTests _sharedResourcesWrapperMock.Setup(x => x.SendString("GG", false)).Returns("-1.0"); _sharedResourcesWrapperMock.Setup(x => x.SendString("Gg", false)).Returns(telescopeLongitude); - _utilMock.Setup(x => x.DMSToDegrees(telescopeLongitude)).Returns(telescopeLongitudeValue); - + _utilMock.Setup(x => x.DMSToDegrees(telescopeLongitude)).Returns(telescopeLongitudeValue); + ConnectTelescope(); diff --git a/Meade.net.Telescope/Telescope.cs b/Meade.net.Telescope/Telescope.cs index 137151f..c4f65dc 100644 --- a/Meade.net.Telescope/Telescope.cs +++ b/Meade.net.Telescope/Telescope.cs @@ -57,7 +57,8 @@ namespace ASCOM.Meade.net private readonly IUtilExtra _utilitiesExtra; /// - /// Private variable to hold an ASCOM AstroUtilities object to provide the Range method + /// Private variable to hold an ASCOM AstroUtilities object to provide the method + /// and /// private readonly IAstroUtils _astroUtilities; @@ -65,6 +66,8 @@ namespace ASCOM.Meade.net private readonly IClock _clock; + private readonly INOVAS31 _novas; + /// /// Private variable to hold number of decimals for RA /// @@ -76,7 +79,7 @@ namespace ASCOM.Meade.net private int _digitsDe = 2; private short _settleTime; - + /// /// Initializes a new instance of the class. /// Must be public for COM registration. @@ -92,6 +95,7 @@ namespace ASCOM.Meade.net _astroUtilities = new AstroUtils(); // Initialise astro utilities object _astroMaths = new AstroMaths.AstroMaths(); _clock = new Clock(); + _novas = new NOVAS31(); Initialise(nameof(Telescope)); } @@ -125,7 +129,7 @@ namespace ASCOM.Meade.net } public Telescope(IUtil util, IUtilExtra utilExtra, IAstroUtils astroUtilities, - ISharedResourcesWrapper sharedResourcesWrapper, IAstroMaths astroMaths, IClock clock) : base( + ISharedResourcesWrapper sharedResourcesWrapper, IAstroMaths astroMaths, IClock clock, INOVAS31 novas) : base( sharedResourcesWrapper) { _clock = clock; @@ -133,6 +137,7 @@ namespace ASCOM.Meade.net _utilitiesExtra = utilExtra; //Initialise util object _astroUtilities = astroUtilities; // Initialise astro utilities object _astroMaths = astroMaths; + _novas = novas; Initialise(nameof(Telescope)); } @@ -607,6 +612,18 @@ namespace ASCOM.Meade.net return false; } + // true iff the mount will perform a meridian flip when required + // TODO: Needs checking what mounts actually support this + private bool IsMeridianFlipOnSlewSupported() + { + if (SharedResourcesWrapper.ProductName == TelescopeList.LX200CLASSIC) + { + return false; + } + + return true; + } + private bool FirmwareIsGreaterThan(string minVersion) { var currentVersion = SharedResourcesWrapper.FirmwareVersion; @@ -1099,7 +1116,7 @@ namespace ASCOM.Meade.net return false; } } - + public bool AtPark { get @@ -1368,8 +1385,18 @@ namespace ASCOM.Meade.net public PierSide DestinationSideOfPier(double rightAscension, double declination) { - LogMessage("DestinationSideOfPier Get", "Not implemented"); - throw new MethodNotImplementedException("DestinationSideOfPier"); + CheckConnected("DestinationSideOfPier"); + + double hourAngle = _astroUtilities.ConditionHA(SiderealTime - rightAscension); + + var destinationSOP = hourAngle > 0 + ? PierSide.pierEast : + (hourAngle < 0 ? PierSide.pierWest : SideOfPier); + + LogMessage("DestinationSideOfPier", + $"Destination SOP of RA {rightAscension.ToString(CultureInfo.InvariantCulture)} is {destinationSOP}"); + + return destinationSOP; } public bool DoesRefraction @@ -1433,7 +1460,7 @@ namespace ASCOM.Meade.net //sidereal speed(approx 15.0417”/sec)[Autostar II only] //Returns: Nothing - //info from RickB says that 15.04107 is a better value for + //info from RickB says that 15.04107 is a better value for GuideRate = value; @@ -1464,7 +1491,7 @@ namespace ASCOM.Meade.net SetNewGuideRate(newValue, "GuideRateDeclination"); } } - + public double GuideRateRightAscension { get @@ -1493,7 +1520,7 @@ namespace ASCOM.Meade.net private bool _movingPrimary; private bool _movingSecondary; - + public void MoveAxis(TelescopeAxes axis, double rate) { LogMessage("MoveAxis", $"Axis={axis} rate={rate}"); @@ -1555,12 +1582,16 @@ namespace ASCOM.Meade.net //:Me# Move Telescope East at current slew rate //Returns: Nothing _movingPrimary = true; + // in principle we could calculate the current side of pier, but unknown is the safer option. + _pierSide = PierSide.pierUnknown; break; case ComparisonResult.Lower: SharedResourcesWrapper.SendBlind("Mw"); //:Mw# Move Telescope West at current slew rate //Returns: Nothing _movingPrimary = true; + // in principle we could calculate the current side of pier, but unknown is the safer option. + _pierSide = PierSide.pierUnknown; break; } break; @@ -1763,7 +1794,7 @@ namespace ASCOM.Meade.net var hms = $"{token[0]}:{seconds}"; return _utilities.HMSToHours(hms); } - + double _lastGoodRightAsension; public double RightAscension @@ -1792,9 +1823,9 @@ namespace ASCOM.Meade.net if (parkedPosition != null) return parkedPosition.RightAscension; - throw; + throw; } - + } } @@ -1820,10 +1851,25 @@ namespace ASCOM.Meade.net throw new MethodNotImplementedException("SetPark"); } + /// + /// Start with . + /// As we do not know the physical declination axis position, we have to keep track manually. + /// + private PierSide _pierSide = PierSide.pierUnknown; public PierSide SideOfPier { get { + if (IsMeridianFlipOnSlewSupported()) + { + // while mount is slewing return unknown, this is required since + // DoSlewAsync updates _pierSide before slew is finished + var pierSide = Slewing ? PierSide.pierUnknown : _pierSide; + + LogMessage("SideOfPier", "Get - " + pierSide); + return pierSide; + } + LogMessage("SideOfPier Get", "Not implemented"); throw new PropertyNotImplementedException("SideOfPier", false); } @@ -1839,15 +1885,20 @@ namespace ASCOM.Meade.net { get { + CheckConnected("SiderealTime Get"); + // Now using NOVAS 3.1 double siderealTime = 0.0; - using (var novas = new NOVAS31()) + + var jd = _utilities.DateUTCToJulian(_clock.UtcNow); + var siderealTimeResult = _novas.SiderealTime(jd, 0, _novas.DeltaT(jd), + GstType.GreenwichApparentSiderealTime, + Method.EquinoxBased, + Accuracy.Reduced, ref siderealTime); + + if (siderealTimeResult != 0) { - var jd = _utilities.DateUTCToJulian(_clock.UtcNow); - novas.SiderealTime(jd, 0, novas.DeltaT(jd), - GstType.GreenwichApparentSiderealTime, - Method.EquinoxBased, - Accuracy.Reduced, ref siderealTime); + throw new InvalidOperationException($"NOVAS 3.1 SiderealTime returned: {siderealTimeResult} in SiderealTime"); } // Allow for the longitude @@ -1881,7 +1932,7 @@ namespace ASCOM.Meade.net LogMessage("SiteElevation", "Set: no change detected"); return; } - + LogMessage("SiteElevation", $"Set: {value} was {base.SiteElevation}"); base.SiteElevation = value; UpdateSiteElevation(); @@ -2052,7 +2103,7 @@ namespace ASCOM.Meade.net _utilities.WaitForMilliseconds(200); //be responsive to AbortSlew(); } } - + public void SlewToAltAzAsync(double azimuth, double altitude) { CheckConnected("SlewToAltAzAsync"); @@ -2116,15 +2167,23 @@ namespace ASCOM.Meade.net case "0": //We're slewing everything should be working just fine. LogMessage("DoSlewAsync", "Slewing to target"); + + if (IsMeridianFlipOnSlewSupported()) + { + // Update side of pier to destination side of pier + // Assumption: Mount will do meridian flip if required + _pierSide = DestinationSideOfPier(TargetRightAscension, TargetDeclination); + } + SetSlewingMinEndTime(); break; case "1": - //Below Horizon + //Below Horizon string belowHorizonMessage = SharedResourcesWrapper.ReadTerminated(); LogMessage("DoSlewAsync", $"Slew failed \"{belowHorizonMessage}\""); throw new InvalidOperationException(belowHorizonMessage); case "2": - //Below minimum elevation + //Below minimum elevation string belowMinimumElevationMessage = SharedResourcesWrapper.ReadTerminated(); LogMessage("DoSlewAsync", $"Slew failed \"{belowMinimumElevationMessage}\""); throw new InvalidOperationException(belowMinimumElevationMessage); @@ -2362,7 +2421,7 @@ namespace ASCOM.Meade.net LogMessage("SyncToTarget", "Executing"); CheckConnected("SyncToTarget"); CheckParked(); - + var result = SharedResourcesWrapper.SendString("CM"); //:CM# Synchronizes the telescope's position with the currently selected database object's coordinates. //Returns: @@ -2480,7 +2539,7 @@ namespace ASCOM.Meade.net throw new InvalidValueException("Right ascension value cannot be greater than 23:59:59"); var hms = IsLongFormat ? - _utilities.HoursToHMS(value, ":", ":", ":", _digitsRa) : + _utilities.HoursToHMS(value, ":", ":", ":", _digitsRa) : _utilities.HoursToHM(value, ":", "", _digitsRa).Replace(',','.'); var command = $"Sr{hms}"; @@ -2696,7 +2755,7 @@ namespace ASCOM.Meade.net throw new InvalidOperationException("Failed to set local date"); } - //throwing away these two strings which represent + //throwing away these two strings which represent SharedResourcesWrapper.ReadTerminated(); //Updating Planetary Data# SharedResourcesWrapper.ReadTerminated(); // # }); @@ -2721,6 +2780,9 @@ namespace ASCOM.Meade.net BypassHandboxEntryForAutostarII(); SharedResourcesWrapper.SetParked(false, null); + + // reset side of pier + SideOfPier = PierSide.pierUnknown; } private bool BypassHandboxEntryForAutostarII() @@ -2747,7 +2809,7 @@ namespace ASCOM.Meade.net #region ASCOM Registration // Register or unregister driver for ASCOM. This is harmless if already - // registered or unregistered. + // registered or unregistered. // /// /// Register or unregister the driver with the ASCOM Platform.