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 38fde76..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)); } @@ -339,6 +344,7 @@ namespace ASCOM.Meade.net public void CommandBlind(string command, bool raw) { + LogMessage("CommandBlind", "raw: {0} command {0}", raw, command); CheckConnected("CommandBlind"); // Call CommandString and return as soon as it finishes //this.CommandString(command, raw); @@ -346,14 +352,16 @@ namespace ASCOM.Meade.net // or //throw new ASCOM.MethodNotImplementedException("CommandBlind"); // DO NOT have both these sections! One or the other + LogMessage("CommandBlind", "Completed"); } public bool CommandBool(string command, bool raw) { + LogMessage("CommandBool", "raw: {0} command {0}", raw, command); CheckConnected("CommandBool"); - //string ret = CommandString(command, raw); - return SharedResourcesWrapper.SendBool(command, raw); - // TODO decode the return string and return true or false + var result = SharedResourcesWrapper.SendBool(command, raw); + LogMessage("CommandBool", "Completed: {0}", result); + return result; // or //throw new MethodNotImplementedException("CommandBool"); // DO NOT have both these sections! One or the other @@ -361,11 +369,14 @@ namespace ASCOM.Meade.net public string CommandString(string command, bool raw) { + LogMessage("CommandString", "raw: {0} command {0}", raw, command); 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, raw); + var result = SharedResourcesWrapper.SendString(command, raw); + LogMessage("CommandBool", "Completed: {0}", result); + return result; //throw new ASCOM.MethodNotImplementedException("CommandString"); } @@ -601,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; @@ -1093,7 +1116,7 @@ namespace ASCOM.Meade.net return false; } } - + public bool AtPark { get @@ -1362,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 @@ -1427,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; @@ -1458,7 +1491,7 @@ namespace ASCOM.Meade.net SetNewGuideRate(newValue, "GuideRateDeclination"); } } - + public double GuideRateRightAscension { get @@ -1487,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}"); @@ -1549,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; @@ -1757,7 +1794,7 @@ namespace ASCOM.Meade.net var hms = $"{token[0]}:{seconds}"; return _utilities.HMSToHours(hms); } - + double _lastGoodRightAsension; public double RightAscension @@ -1786,9 +1823,9 @@ namespace ASCOM.Meade.net if (parkedPosition != null) return parkedPosition.RightAscension; - throw; + throw; } - + } } @@ -1814,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); } @@ -1833,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 @@ -1875,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(); @@ -2046,7 +2103,7 @@ namespace ASCOM.Meade.net _utilities.WaitForMilliseconds(200); //be responsive to AbortSlew(); } } - + public void SlewToAltAzAsync(double azimuth, double altitude) { CheckConnected("SlewToAltAzAsync"); @@ -2110,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); @@ -2356,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: @@ -2474,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}"; @@ -2690,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(); // # }); @@ -2715,6 +2780,9 @@ namespace ASCOM.Meade.net BypassHandboxEntryForAutostarII(); SharedResourcesWrapper.SetParked(false, null); + + // reset side of pier + SideOfPier = PierSide.pierUnknown; } private bool BypassHandboxEntryForAutostarII() @@ -2741,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. diff --git a/Meade.net.focuser/Focuser.cs b/Meade.net.focuser/Focuser.cs index caa25fc..93e0ca0 100644 --- a/Meade.net.focuser/Focuser.cs +++ b/Meade.net.focuser/Focuser.cs @@ -101,6 +101,7 @@ namespace ASCOM.Meade.net public void CommandBlind(string command, bool raw) { + LogMessage("CommandBlind", "raw: {0} command {0}", raw, command); CheckConnected("CommandBlind"); // Call CommandString and return as soon as it finishes //this.CommandString(command, raw); @@ -108,14 +109,16 @@ namespace ASCOM.Meade.net // or //throw new ASCOM.MethodNotImplementedException("CommandBlind"); // DO NOT have both these sections! One or the other + LogMessage("CommandBlind", "Completed"); } public bool CommandBool(string command, bool raw) { + LogMessage("CommandBool", "raw: {0} command {0}", raw, command); CheckConnected("CommandBool"); - //string ret = CommandString(command, raw); - return SharedResourcesWrapper.SendBool(command, raw); - // decode the return string and return true or false + var result = SharedResourcesWrapper.SendBool(command, raw); + LogMessage("CommandBool", "Completed: {0}", result); + return result; // or //throw new MethodNotImplementedException("CommandBool"); // DO NOT have both these sections! One or the other @@ -123,11 +126,14 @@ namespace ASCOM.Meade.net public string CommandString(string command, bool raw) { + LogMessage("CommandString", "raw: {0} command {0}", raw, command); 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, raw); + var result = SharedResourcesWrapper.SendString(command, raw); + LogMessage("CommandBool", "Completed: {0}", result); + return result; //throw new ASCOM.MethodNotImplementedException("CommandString"); }