From 79a77d4e1dd7850350877eff4690f80f2e8373e0 Mon Sep 17 00:00:00 2001 From: Colin Dawson Date: Fri, 17 May 2019 14:21:26 +0000 Subject: [PATCH] Merged in feature/LocalServer (pull request #5) Feature/LocalServer * Major refactor. Switching over to a local server hub style driver allowing multiple programs to control the telescope at one time without the need for the POTH Hub * Unified the setup dialog * Implemented shared serial port, Both Telescope and Driver can connect at the same time. * Ported the focuser implementation from the non server based version. * Ported the telescope driver code. * Fixed problem with # not being stripped from the returned string ends. Fixed issue with RA being returned as degress rather than hours. * Telescope passes validation * Added a lock around the focuser move. * Reimplemented CommandBlind and CommandString * Corrected version information * Removed the Altitude support as there's a bug in the Autostar and Audiostar firmware --- ASCOM.MeadeAutostar497.Focuser.Validation.txt | 68 - ....MeadeAutostar497.Telescope.Validation.txt | 268 --- ASCOM.MeadeGeneric.Telescope.Validation.txt | 269 +++ ASCOM.MeadeGeneric.focuser.Validation.txt | 69 + ConformanceResult.txt | 272 --- FocuserTestConsole/FocuserTestConsole.csproj | 64 + FocuserTestConsole/Program.cs | 55 + .../Properties/AssemblyInfo.cs | 26 +- .../app.config | 2 +- .../ASCOM.ico | Bin .../ASCOM.png | Bin .../ASCOMDriverTemplate.snk | Bin .../Meade.net.Telescope.csproj | 45 +- .../Properties/AssemblyInfo.cs | 14 +- .../Properties/Resources.Designer.cs | 4 +- .../Properties/Resources.resx | 0 .../Properties/Settings.Designer.cs | 2 +- .../Properties/Settings.settings | 0 .../Rates.cs | 18 +- .../ReadMe.htm | 0 .../Resources/ASCOM.bmp | Bin .../StringExtensions.cs | 10 +- Meade.net.Telescope/Telescope.cs | 1672 +++++++++++++++++ .../app.config | 2 +- Meade.net.focuser/ASCOM.ico | Bin 0 -> 125783 bytes Meade.net.focuser/ASCOM.png | Bin 0 -> 1922 bytes Meade.net.focuser/ASCOMDriverTemplate.snk | Bin 0 -> 596 bytes Meade.net.focuser/Focuser.cs | 570 ++++++ Meade.net.focuser/Meade.net.focuser.csproj | 151 ++ Meade.net.focuser/Properties/AssemblyInfo.cs | 39 + .../Properties/Resources.Designer.cs | 83 + Meade.net.focuser/Properties/Resources.resx | 127 ++ .../Properties/Settings.Designer.cs | 26 + .../Properties/Settings.settings | 5 + Meade.net.focuser/ReadMe.htm | 147 ++ Meade.net.focuser/Resources/ASCOM.bmp | Bin 0 -> 3382 bytes Meade.net.focuser/app.config | 8 + Meade.net.sln | 73 + Meade.net/ASCOM.ico | Bin 0 -> 125783 bytes Meade.net/ASCOM.png | Bin 0 -> 1922 bytes Meade.net/ClassFactory.cs | 244 +++ Meade.net/GarbageCollection.cs | 57 + Meade.net/LocalServer.cs | 642 +++++++ Meade.net/LocalServer.snk | Bin 0 -> 596 bytes Meade.net/Meade.net.csproj | 153 ++ Meade.net/Properties/AssemblyInfo.cs | 27 + Meade.net/Properties/Resources.Designer.cs | 73 + Meade.net/Properties/Resources.resx | 124 ++ Meade.net/ReadMe.htm | 666 +++++++ Meade.net/ReferenceCountedObject.cs | 24 + Meade.net/Resources/ASCOM.bmp | Bin 0 -> 3382 bytes .../SetupDialogForm.cs | 35 +- .../SetupDialogForm.designer.cs | 7 +- .../SetupDialogForm.resx | 0 Meade.net/SharedResources.cs | 382 ++++ Meade.net/app.config | 3 + Meade.net/frmMain.Designer.cs | 63 + Meade.net/frmMain.cs | 20 + Meade.net/frmMain.resx | 120 ++ .../BootstrapAscomProfileStore.ps1 | 36 - .../MeadeAutostar497.UnitTests.csproj | 126 -- .../TelescopeControllerUnitTests.cs | 556 ------ MeadeAutostar497.UnitTests/packages.config | 9 - MeadeAutostar497.sln | 49 - MeadeAutostar497/AscomClasses/Telescope.cs | 1226 ------------ .../Controller/FirmwareVersion.cs | 34 - .../Controller/ISerialProcessor.cs | 28 - .../Controller/ITelescopeController.cs | 42 - .../Controller/SerialProcessor.cs | 142 -- .../Controller/TelescopeController.cs | 992 ---------- TargetToBeat.txt | 249 --- .../Program.cs | 30 +- .../Properties/AssemblyInfo.cs | 4 +- .../TelescopeTestConsole.csproj | 8 +- TelescopeTestConsole/app.config | 3 + 75 files changed, 6043 insertions(+), 4220 deletions(-) delete mode 100644 ASCOM.MeadeAutostar497.Focuser.Validation.txt delete mode 100644 ASCOM.MeadeAutostar497.Telescope.Validation.txt create mode 100644 ASCOM.MeadeGeneric.Telescope.Validation.txt create mode 100644 ASCOM.MeadeGeneric.focuser.Validation.txt delete mode 100644 ConformanceResult.txt create mode 100644 FocuserTestConsole/FocuserTestConsole.csproj create mode 100644 FocuserTestConsole/Program.cs rename {MeadeAutostar497.UnitTests => FocuserTestConsole}/Properties/AssemblyInfo.cs (68%) rename {TestConsole => FocuserTestConsole}/app.config (71%) rename {MeadeAutostar497 => Meade.net.Telescope}/ASCOM.ico (100%) rename {MeadeAutostar497 => Meade.net.Telescope}/ASCOM.png (100%) rename {MeadeAutostar497 => Meade.net.Telescope}/ASCOMDriverTemplate.snk (100%) rename MeadeAutostar497/MeadeAutostar497.csproj => Meade.net.Telescope/Meade.net.Telescope.csproj (81%) rename {MeadeAutostar497 => Meade.net.Telescope}/Properties/AssemblyInfo.cs (75%) rename {MeadeAutostar497 => Meade.net.Telescope}/Properties/Resources.Designer.cs (95%) rename {MeadeAutostar497 => Meade.net.Telescope}/Properties/Resources.resx (100%) rename {MeadeAutostar497 => Meade.net.Telescope}/Properties/Settings.Designer.cs (95%) rename {MeadeAutostar497 => Meade.net.Telescope}/Properties/Settings.settings (100%) rename {MeadeAutostar497/AscomClasses => Meade.net.Telescope}/Rates.cs (93%) rename {MeadeAutostar497 => Meade.net.Telescope}/ReadMe.htm (100%) rename {MeadeAutostar497 => Meade.net.Telescope}/Resources/ASCOM.bmp (100%) rename {MeadeAutostar497 => Meade.net.Telescope}/StringExtensions.cs (51%) create mode 100644 Meade.net.Telescope/Telescope.cs rename {MeadeAutostar497 => Meade.net.Telescope}/app.config (93%) create mode 100644 Meade.net.focuser/ASCOM.ico create mode 100644 Meade.net.focuser/ASCOM.png create mode 100644 Meade.net.focuser/ASCOMDriverTemplate.snk create mode 100644 Meade.net.focuser/Focuser.cs create mode 100644 Meade.net.focuser/Meade.net.focuser.csproj create mode 100644 Meade.net.focuser/Properties/AssemblyInfo.cs create mode 100644 Meade.net.focuser/Properties/Resources.Designer.cs create mode 100644 Meade.net.focuser/Properties/Resources.resx create mode 100644 Meade.net.focuser/Properties/Settings.Designer.cs create mode 100644 Meade.net.focuser/Properties/Settings.settings create mode 100644 Meade.net.focuser/ReadMe.htm create mode 100644 Meade.net.focuser/Resources/ASCOM.bmp create mode 100644 Meade.net.focuser/app.config create mode 100644 Meade.net.sln create mode 100644 Meade.net/ASCOM.ico create mode 100644 Meade.net/ASCOM.png create mode 100644 Meade.net/ClassFactory.cs create mode 100644 Meade.net/GarbageCollection.cs create mode 100644 Meade.net/LocalServer.cs create mode 100644 Meade.net/LocalServer.snk create mode 100644 Meade.net/Meade.net.csproj create mode 100644 Meade.net/Properties/AssemblyInfo.cs create mode 100644 Meade.net/Properties/Resources.Designer.cs create mode 100644 Meade.net/Properties/Resources.resx create mode 100644 Meade.net/ReadMe.htm create mode 100644 Meade.net/ReferenceCountedObject.cs create mode 100644 Meade.net/Resources/ASCOM.bmp rename {MeadeAutostar497/AscomClasses => Meade.net}/SetupDialogForm.cs (66%) rename {MeadeAutostar497/AscomClasses => Meade.net}/SetupDialogForm.designer.cs (96%) rename {MeadeAutostar497/AscomClasses => Meade.net}/SetupDialogForm.resx (100%) create mode 100644 Meade.net/SharedResources.cs create mode 100644 Meade.net/app.config create mode 100644 Meade.net/frmMain.Designer.cs create mode 100644 Meade.net/frmMain.cs create mode 100644 Meade.net/frmMain.resx delete mode 100644 MeadeAutostar497.UnitTests/BootstrapAscomProfileStore.ps1 delete mode 100644 MeadeAutostar497.UnitTests/MeadeAutostar497.UnitTests.csproj delete mode 100644 MeadeAutostar497.UnitTests/TelescopeControllerUnitTests.cs delete mode 100644 MeadeAutostar497.UnitTests/packages.config delete mode 100644 MeadeAutostar497.sln delete mode 100644 MeadeAutostar497/AscomClasses/Telescope.cs delete mode 100644 MeadeAutostar497/Controller/FirmwareVersion.cs delete mode 100644 MeadeAutostar497/Controller/ISerialProcessor.cs delete mode 100644 MeadeAutostar497/Controller/ITelescopeController.cs delete mode 100644 MeadeAutostar497/Controller/SerialProcessor.cs delete mode 100644 MeadeAutostar497/Controller/TelescopeController.cs delete mode 100644 TargetToBeat.txt rename {TestConsole => TelescopeTestConsole}/Program.cs (71%) rename {TestConsole => TelescopeTestConsole}/Properties/AssemblyInfo.cs (93%) rename TestConsole/TestConsole.csproj => TelescopeTestConsole/TelescopeTestConsole.csproj (92%) create mode 100644 TelescopeTestConsole/app.config diff --git a/ASCOM.MeadeAutostar497.Focuser.Validation.txt b/ASCOM.MeadeAutostar497.Focuser.Validation.txt deleted file mode 100644 index 8dedf49..0000000 --- a/ASCOM.MeadeAutostar497.Focuser.Validation.txt +++ /dev/null @@ -1,68 +0,0 @@ -Conform Report Hash (V1): 55AB333E4394ADF3F35929567EAB46A092F3574DF38E1F00BF586967B7EE826AA2D92647409F699E62954B07AF3389CEA7DB7FAAFFE04E05BE358A744E95A624 - - -ConformanceCheck ASCOM Device Conformance Checker Version 6.4.63.0, Build time: 18/12/2018 08:58:34 -ConformanceCheck Running on: ASCOM Platform 6.4 SP1 6.4.1.2695 - -ConformanceCheck Driver ProgID: ASCOM.MeadeAutostar497.Telescope - -Error handling -Error number for "Not Implemented" is: 80040400 -Error number for "Invalid Value 1" is: 80040404 -Error number for "Value Not Set 1" is: 80040402 -Error number for "Value Not Set 2" is: 80040403 -Error messages will not be interpreted to infer state. - -00:21:34.375 Driver Access Checks OK -00:21:35.008 AccessChecks OK Successfully created driver using late binding -00:21:35.253 AccessChecks OK Successfully connected using late binding -00:21:35.257 AccessChecks INFO The driver is a .NET object -00:21:35.260 AccessChecks INFO The AssemblyQualifiedName is: ASCOM.MeadeAutostar497.Telescope, ASCOM.MeadeAutostar497.Telescope, Version= -00:21:35.265 AccessChecks INFO The driver implements interface: ASCOM.DeviceInterface.ITelescopeV3 -00:21:35.269 AccessChecks INFO The driver implements interface: ASCOM.DeviceInterface.IFocuserV3 -00:21:35.992 AccessChecks INFO Device does not expose IFocuser interface -00:21:36.147 AccessChecks INFO Device does not expose IFocuserV2 interface -00:21:36.479 AccessChecks INFO Device exposes IFocuserV3 interface -00:21:36.692 AccessChecks OK Successfully created driver using driver access toolkit -00:21:36.895 AccessChecks OK Successfully connected using driver access toolkit -00:21:36.958 AccessChecks OK Successfully disconnected using driver access toolkit - -Conform is using ASCOM.DriverAccess.Focuser to get a Focuser object -00:21:37.078 ConformanceCheck OK Driver instance created successfully -00:21:37.311 ConformanceCheck OK Connected OK - -Common Driver Methods -00:21:37.348 InterfaceVersion OK 3 -00:21:37.374 Connected OK True -00:21:37.401 Description OK Meade Autostar 497 .net -00:21:37.428 DriverInfo OK Information about the driver itself. Version: 0.2 -00:21:37.457 DriverVersion OK 0.2 -00:21:37.485 Name OK Meade Autostar 497 .net -00:21:37.511 CommandXXX INFO Tests skipped -00:21:37.515 Action INFO Conform cannot test the Action method -00:21:37.521 SupportedActions OK Driver returned an empty action list - -Properties -00:21:37.626 Absolute OK False -00:21:37.631 IsMoving OK False -00:21:37.637 MaxStep OK 7000 -00:21:37.642 MaxIncrement OK 7000 -00:21:37.820 Position OK Position must not be implemented for a relative focuser and a PropertyNotImplementedException exception was generated as expected -00:21:37.988 StepSize OK Optional member threw a PropertyNotImplementedException exception. -00:21:37.993 TempCompAvailable OK False -00:21:37.999 TempComp Read OK False -00:21:38.167 TempComp Write OK Temperature compensation is not available and a PropertyNotImplementedException exception was generated as expected -00:21:38.504 Temperature OK Optional member threw a PropertyNotImplementedException exception. - -Methods -00:21:38.544 Halt OK Focuser halted OK -00:21:38.552 Move - TempComp False Moving by: 700 -00:21:39.264 Move - TempComp False Asynchronous move found -00:21:39.270 Move - TempComp False OK Relative move OK -00:21:39.278 Move - TempComp False INFO Returning to original position: 0 - -Conformance test complete - -No errors, warnings or issues found: your driver passes ASCOM validation!! - -Driver Hash Value: DBF36156F87DA91F130E0AABEFE80B250D62C2DB689AD8B44243AE0D9322245CF15AAE2E663962402E87B6B1A15440A6DDB46562EFB9527E3596096319C2AC85 diff --git a/ASCOM.MeadeAutostar497.Telescope.Validation.txt b/ASCOM.MeadeAutostar497.Telescope.Validation.txt deleted file mode 100644 index eff5811..0000000 --- a/ASCOM.MeadeAutostar497.Telescope.Validation.txt +++ /dev/null @@ -1,268 +0,0 @@ -Conform Report Hash (V1): 671D83C15427DD14D9FA7F5A6C62D6B21372362C5173A2B95171D14D7442358DCF3950481797D63EB94E642D2563C3094C387C23D65466833FEA5E64CD045B46 - - -ConformanceCheck ASCOM Device Conformance Checker Version 6.4.63.0, Build time: 18/12/2018 08:58:34 -ConformanceCheck Running on: ASCOM Platform 6.4 SP1 6.4.1.2695 - -ConformanceCheck Driver ProgID: ASCOM.MeadeAutostar497.Telescope - -Error handling -Error number for "Not Implemented" is: 80040400 -Error number for "Invalid Value 1" is: 80040401 -Error number for "Invalid Value 2" is: 80040405 -Error number for "Value Not Set 1" is: 80040402 -Error number for "Value Not Set 2" is: 80040403 -Error messages will not be interpreted to infer state. - -18:07:45.625 Driver Access Checks OK -18:07:46.272 AccessChecks OK Successfully created driver using late binding -18:07:46.685 AccessChecks OK Successfully connected using late binding -18:07:46.689 AccessChecks INFO The driver is a .NET object -18:07:46.693 AccessChecks INFO The AssemblyQualifiedName is: ASCOM.MeadeAutostar497.Telescope, ASCOM.MeadeAutostar497.Telescope, Version= -18:07:46.697 AccessChecks INFO The driver implements interface: ASCOM.DeviceInterface.ITelescopeV3 -18:07:46.701 AccessChecks INFO The driver implements interface: ASCOM.DeviceInterface.IFocuserV3 -18:07:47.416 AccessChecks INFO Device does not expose interface ITelescopeV2 -18:07:48.387 AccessChecks INFO Device exposes interface ITelescopeV3 -18:07:49.708 AccessChecks OK Successfully created driver using driver access toolkit -18:07:50.029 AccessChecks OK Successfully connected using driver access toolkit - -Conform is using ASCOM.DriverAccess.Telescope to get a Telescope object -18:07:51.351 ConformanceCheck OK Driver instance created successfully -18:07:51.775 ConformanceCheck OK Connected OK - -Common Driver Methods -18:07:51.817 InterfaceVersion OK 3 -18:07:51.845 Connected OK True -18:07:51.874 Description OK Meade Autostar 497 .net -18:07:51.903 DriverInfo OK Information about the driver itself. Version: 0.0 -18:07:51.932 DriverVersion OK 0.0 -18:07:51.961 Name OK Meade Autostar 497 .net -18:07:51.990 CommandString INFO Conform cannot test the CommandString method -18:07:51.996 CommandBlind INFO Conform cannot test the CommandBlind method -18:07:52.002 CommandBool INFO Conform cannot test the CommandBool method -18:07:52.008 Action INFO Conform cannot test the Action method -18:07:52.015 SupportedActions OK Driver returned an empty action list - -Can Properties -18:07:52.082 CanFindHome OK False -18:07:52.089 CanPark OK True -18:07:52.096 CanPulseGuide OK True -18:07:52.102 CanSetDeclinationRate OK False -18:07:52.109 CanSetGuideRates OK False -18:07:52.117 CanSetPark OK False -18:07:52.125 CanSetPierSide OK False -18:07:52.177 CanSetRightAscensionRate OK False -18:07:52.185 CanSetTracking OK False -18:07:52.193 CanSlew OK True -18:07:52.200 CanSlewltAz OK True -18:07:52.208 CanSlewAltAzAsync OK True -18:07:52.216 CanSlewAsync OK True -18:07:52.224 CanSync OK True -18:07:52.231 CanSyncAltAz OK False -18:07:52.239 CanUnPark OK False - -Pre-run Checks -18:07:52.286 Mount Safety INFO Scope is not parked, continuing testing -18:07:52.339 TimeCheck INFO PC Time Zone: GMT Summer Time, offset -1 hours. -18:07:52.348 TimeCheck INFO PC UTCDate: 08-May-2019 17:07:52.347 -18:07:53.254 TimeCheck INFO Mount UTCDate: 02-May-2019 19:33:55.000 - -Properties -18:07:53.358 AlignmentMode OK algPolar -18:07:53.515 Altitude OK 1.00 -18:07:53.555 ApertureArea OK Optional member threw a PropertyNotImplementedException exception. -18:07:53.591 ApertureDiameter OK Optional member threw a PropertyNotImplementedException exception. -18:07:53.622 AtHome OK False -18:07:53.654 AtPark OK False -18:07:53.847 Azimuth OK 45.67 -18:07:54.028 Declination OK -01:00:01.00 -18:07:54.060 DeclinationRate Read OK 0.00 -18:07:54.093 DeclinationRate Write OK CanSetDeclinationRate is False and a PropertyNotImplementedException exception was generated as expected -18:07:54.126 DoesRefraction Read OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.159 DoesRefraction Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.193 EquatorialSystem OK equLocalTopocentric -18:07:54.227 FocalLength OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.260 GuideRateDeclination Read OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.270 GuideRateDeclination Write OK CanSetGuideRates is False and a PropertyNotImplementedException exception was generated as expected -18:07:54.303 GuideRateRightAscension Read OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.314 GuideRateRightAscension Write OK CanSetGuideRates is False and a PropertyNotImplementedException exception was generated as expected -18:07:54.347 IsPulseGuiding OK False -18:07:54.541 RightAscension OK 03:59:09.00 -18:07:54.575 RightAscensionRate Read OK 0.00 -18:07:54.609 RightAscensionRate Write OK CanSetRightAscensionRate is False and a PropertyNotImplementedException exception was generated as expected -18:07:54.644 SiteElevation Read OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.678 SiteElevation Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.691 SiteElevation Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.703 SiteElevation Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.861 SiteLatitude Read OK 00:00:00.00 -18:07:54.900 SiteLatitude Write OK Invalid Value exception generated as expected on set site latitude < -90 degrees -18:07:54.912 SiteLatitude Write OK Invalid Value exception generated as expected on set site latitude > 90 degrees -18:07:55.315 SiteLatitude Write OK Legal value 00:00:00.00 degrees written successfully -18:07:55.455 SiteLongitude Read OK -42:12:00.00 -18:07:55.490 SiteLongitude Write OK Invalid Value exception generated as expected on set site longitude < -180 degrees -18:07:55.502 SiteLongitude Write OK Invalid Value exception generated as expected on set site longitude > 180 degrees -18:07:56.090 SiteLongitude Write OK Legal value -42:12:00.00 degrees written successfully -18:07:56.246 Slewing OK False -18:07:56.280 SlewSettleTime Read OK Optional member threw a PropertyNotImplementedException exception. -18:07:56.315 SlewSettleTime Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:56.328 SlewSettleTime Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:56.363 SideOfPier Read OK Optional member threw a PropertyNotImplementedException exception. -18:07:56.578 SiderealTime OK 05:17:41.24 -18:07:56.590 SiderealTime INFO Scope and ASCOM sidereal times are up to 0.5 hour different, Scope: 05:17:41.24, ASCOM: 05:24:06.50 -18:07:56.626 TargetDeclination Read OK .NET Not InvalidOperationException generated on read before write -18:07:56.661 TargetDeclination Write INFO Tests moved after the SlewToCoordinates tests so that Conform can check they properly set target coordinates. -18:07:56.673 TargetRightAscension Read OK .NET Not InvalidOperationException generated on read before write -18:07:56.708 TargetRightAscension Write INFO Tests moved after the SlewToCoordinates tests so that Conform can check they properly set target coordinates. -18:07:56.720 Tracking Read OK True -18:07:56.756 Tracking Write OK CanSetTracking is False and a PropertyNotImplementedException exception was generated as expected -18:07:56.797 TrackingRates Found drive rate: driveSidereal -18:07:56.809 TrackingRates OK Drive rates read OK -18:07:56.822 TrackingRates OK Disposed tracking rates OK -18:07:56.858 TrackingRates OK Successfully obtained a TrackingRates object after the previous TrackingRates object was disposed -18:07:57.039 TrackingRate Read OK driveLunar -18:07:57.075 TrackingRate Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:57.088 TrackingRate Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:57.624 UTCDate Read OK 02-May-2019 19:33:59.000 -18:07:58.626 UTCDate Write OK New UTCDate written successfully: 02/05/2019 20:33:59 - -Methods -18:07:59.997 CanMoveAxis:Primary OK CanMoveAxis:Primary True -18:08:00.035 CanMoveAxis:Secondary OK CanMoveAxis:Secondary True -18:08:00.072 CanMoveAxis:Tertiary OK CanMoveAxis:Tertiary False -18:08:00.108 Park/Unpark INFO Tests skipped -18:08:00.131 AbortSlew OK AbortSlew OK when not slewing -18:08:00.199 AxisRate:Primary OK Axis rate minimum: 1 Axis rate maximum: 1 -18:08:00.211 AxisRate:Primary OK Axis rate minimum: 2 Axis rate maximum: 2 -18:08:00.225 AxisRate:Primary OK Axis rate minimum: 3 Axis rate maximum: 3 -18:08:00.239 AxisRate:Primary OK Axis rate minimum: 4 Axis rate maximum: 4 -18:08:00.253 AxisRate:Primary OK No overlapping axis rates found -18:08:00.266 AxisRate:Primary OK No duplicate axis rates found -18:08:00.279 AxisRate:Primary OK Successfully disposed of rate 1 - 1 -18:08:00.295 AxisRate:Primary OK Successfully disposed of rate 2 - 2 -18:08:00.310 AxisRate:Primary OK Successfully disposed of rate 3 - 3 -18:08:00.322 AxisRate:Primary OK Successfully disposed of rate 4 - 4 -18:08:00.336 AxisRate:Primary OK Disposed axis rates OK -18:08:00.350 AxisRate:Secondary OK Axis rate minimum: 1 Axis rate maximum: 1 -18:08:00.362 AxisRate:Secondary OK Axis rate minimum: 2 Axis rate maximum: 2 -18:08:00.375 AxisRate:Secondary OK Axis rate minimum: 3 Axis rate maximum: 3 -18:08:00.387 AxisRate:Secondary OK Axis rate minimum: 4 Axis rate maximum: 4 -18:08:00.400 AxisRate:Secondary OK No overlapping axis rates found -18:08:00.414 AxisRate:Secondary OK No duplicate axis rates found -18:08:00.445 AxisRate:Secondary OK Successfully disposed of rate 1 - 1 -18:08:00.458 AxisRate:Secondary OK Successfully disposed of rate 2 - 2 -18:08:00.472 AxisRate:Secondary OK Successfully disposed of rate 3 - 3 -18:08:00.487 AxisRate:Secondary OK Successfully disposed of rate 4 - 4 -18:08:00.501 AxisRate:Secondary OK Disposed axis rates OK -18:08:00.517 AxisRate:Tertiary OK Empty axis rate returned -18:08:00.531 AxisRate:Tertiary OK Disposed axis rates OK -18:08:00.550 FindHome OK CanFindHome is False and a MethodNotImplementedException exception was generated as expected -18:08:00.606 MoveAxis Primary OK Can successfully set a movement rate of zero -18:08:00.622 MoveAxis Primary OK Exception correctly generated when move axis is set below lowest rate (0.5) -18:08:00.659 MoveAxis Primary OK Exception correctly generated when move axis is set above highest rate (5) -18:08:04.867 MoveAxis Primary OK Successfully moved axis at minimum rate: 1 -18:08:09.770 MoveAxis Primary OK Successfully moved axis at maximum rate: 4 -18:08:13.981 MoveAxis Primary OK Tracking state correctly restored after MoveAxis when CanSetTracking is false -18:08:13.998 MoveAxis Primary OK AxisRates object successfully disposed -18:08:14.057 MoveAxis Secondary OK Can successfully set a movement rate of zero -18:08:14.072 MoveAxis Secondary OK Exception correctly generated when move axis is set below lowest rate (0.5) -18:08:14.107 MoveAxis Secondary OK Exception correctly generated when move axis is set above highest rate (5) -18:08:18.317 MoveAxis Secondary OK Successfully moved axis at minimum rate: 1 -18:08:23.262 MoveAxis Secondary OK Successfully moved axis at maximum rate: 4 -18:08:27.473 MoveAxis Secondary OK Tracking state correctly restored after MoveAxis when CanSetTracking is false -18:08:27.492 MoveAxis Secondary OK AxisRates object successfully disposed -18:08:27.551 MoveAxis Tertiary OK CanMoveAxis Tertiary is False and a MethodNotImplementedException exception was generated as expected -18:08:29.592 PulseGuide OK Synchronous pulse guide found OK -18:08:54.449 SlewToCoordinates INFO Slewed within 39.2 arc seconds of expected RA: 04:18:14.61, actual RA: 04:18:12.00 -18:08:54.463 SlewToCoordinates INFO Slewed within 7199.0 arc seconds of expected DEC: 01:00:00.00, actual DEC: -00:59:59.00 -18:08:54.478 SlewToCoordinates OK The TargetRightAscension property 04:18:14.61 matches the expected RA OK. -18:08:54.492 SlewToCoordinates OK The TargetDeclination property 01:00:00.00 matches the expected Declination OK. -18:08:54.548 SlewToCoordinates (Bad L) OK Correctly rejected bad RA coordinate: -01:00:00.00 -18:08:55.301 SlewToCoordinates (Bad L) OK Correctly rejected bad Dec coordinate: -100:00:00.00 -18:08:55.360 SlewToCoordinates (Bad H) OK Correctly rejected bad RA coordinate: 25:00:00.00 -18:08:56.199 SlewToCoordinates (Bad H) OK Correctly rejected bad Dec coordinate: 100:00:00.00 -18:09:20.477 SlewToCoordinatesAsync INFO Slewed within 19.3 arc seconds of expected RA: 03:18:41.28, actual RA: 03:18:40.00 -18:09:20.490 SlewToCoordinatesAsync INFO Slewed within 14398.0 arc seconds of expected DEC: 02:00:00.00, actual DEC: -01:59:58.00 -18:09:20.503 SlewToCoordinatesAsync OK The TargetRightAscension property 03:18:41.28 matches the expected RA OK. -18:09:20.516 SlewToCoordinatesAsync OK The TargetDeclination property 02:00:00.00 matches the expected Declination OK. -18:09:20.558 SlewToCoordinatesAsync (Bad L) OK Correctly rejected bad RA coordinate: -01:00:00.00 -18:09:21.381 SlewToCoordinatesAsync (Bad L) OK Correctly rejected bad Dec coordinate: -100:00:00.00 -18:09:21.442 SlewToCoordinatesAsync (Bad H) OK Correctly rejected bad RA coordinate: 25:00:00.00 -18:09:22.292 SlewToCoordinatesAsync (Bad H) OK Correctly rejected bad Dec coordinate: 100:00:00.00 -18:09:44.520 SyncToCoordinates INFO Slewed to start position within 56.8 arc seconds of expected RA: 02:19:07.79, actual RA: 02:19:04.00 -18:09:44.534 SyncToCoordinates OK Slewed to start position OK. DEC: 00:00:00.00 -18:09:47.045 SyncToCoordinates INFO Synced to sync position within 71.8 arc seconds of expected RA: 02:15:07.79, actual RA: 02:15:03.00 -18:09:47.058 SyncToCoordinates INFO Synced to sync position within 3660.0 arc seconds of expected DEC: -01:00:00.00, actual DEC: 00:01:00.00 -18:09:47.071 SyncToCoordinates OK The TargetRightAscension property 02:15:07.79 matches the expected RA OK. -18:09:47.085 SyncToCoordinates OK The TargetDeclination property -01:00:00.00 matches the expected Declination OK. -18:10:08.444 SyncToCoordinates INFO Slewed back to start position within 56.8 arc seconds of expected RA: 02:19:07.79, actual RA: 02:19:04.00 -18:10:08.458 SyncToCoordinates OK Slewed back to start position OK. DEC: 00:00:00.00 -18:10:10.791 SyncToCoordinates INFO Synced to reversed sync position within 71.8 arc seconds of expected RA: 02:23:07.79, actual RA: 02:23:03.00 -18:10:10.806 SyncToCoordinates INFO Synced to reversed sync position within 7200.0 arc seconds of expected DEC: 01:00:00.00, actual DEC: -01:00:00.00 -18:10:32.484 SyncToCoordinates INFO Slewed back to start position within 56.8 arc seconds of expected RA: 02:19:07.79, actual RA: 02:19:04.00 -18:10:32.498 SyncToCoordinates OK Slewed back to start position OK. DEC: 00:00:00.00 -18:10:32.537 SyncToCoordinates (Bad L) OK Correctly rejected bad RA coordinate: -01:00:00.00 -18:10:33.329 SyncToCoordinates (Bad L) OK Correctly rejected bad Dec coordinate: -100:00:00.00 -18:10:33.389 SyncToCoordinates (Bad H) OK Correctly rejected bad RA coordinate: 25:00:00.00 -18:10:34.242 SyncToCoordinates (Bad H) OK Correctly rejected bad Dec coordinate: 100:00:00.00 -18:10:34.301 TargetRightAscension Write OK Invalid Value exception generated as expected on set TargetRightAscension < 0 hours -18:10:34.315 TargetRightAscension Write OK Invalid Value exception generated as expected on set TargetRightAscension > 24 hours -18:10:34.979 TargetRightAscension Write OK Legal value 01:20:19.62 HH:MM:SS written successfully -18:10:35.016 TargetDeclination Write OK Invalid Value exception generated as expected on set TargetDeclination < -90 degrees -18:10:35.032 TargetDeclination Write OK Invalid Value exception generated as expected on set TargetDeclination < -90 degrees -18:10:35.652 TargetDeclination Write OK Legal value 01:00:00.00 DD:MM:SS written successfully -18:10:56.417 SlewToTarget INFO Slewed within 14.8 arc seconds of expected RA: 02:20:20.99, actual RA: 02:20:20.00 -18:10:56.434 SlewToTarget INFO Slewed within 21596.0 arc seconds of expected DEC: 03:00:00.00, actual DEC: -02:59:56.00 -18:10:56.449 SlewToTarget OK The TargetRightAscension property 02:20:20.99 matches the expected RA OK. -18:10:56.463 SlewToTarget OK The TargetDeclination property 03:00:00.00 matches the expected Declination OK. -18:10:56.504 SlewToTarget (Bad L) OK Telescope.TargetRA correctly rejected bad RA coordinate: -01:00:00.00 -18:10:56.676 SlewToTarget (Bad L) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: -100:00:00.00 -18:10:56.736 SlewToTarget (Bad H) OK Telescope.TargetRA correctly rejected bad RA coordinate: 25:00:00.00 -18:10:56.915 SlewToTarget (Bad H) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: 100:00:00.00 -18:11:20.512 SlewToTargetAsync INFO Slewed within 35.1 arc seconds of expected RA: 01:20:42.34, actual RA: 01:20:40.00 -18:11:20.526 SlewToTargetAsync INFO Slewed within 28797.0 arc seconds of expected DEC: 04:00:00.00, actual DEC: -03:59:57.00 -18:11:20.539 SlewToTargetAsync OK The TargetRightAscension property 01:20:42.34 matches the expected RA OK. -18:11:20.553 SlewToTargetAsync OK The TargetDeclination property 04:00:00.00 matches the expected Declination OK. -18:11:20.593 SlewToTargetAsync (Bad L) OK Telescope.TargetRA correctly rejected bad RA coordinate: -01:00:00.00 -18:11:20.774 SlewToTargetAsync (Bad L) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: -100:00:00.00 -18:11:20.833 SlewToTargetAsync (Bad H) OK Telescope.TargetRA correctly rejected bad RA coordinate: 25:00:00.00 -18:11:21.032 SlewToTargetAsync (Bad H) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: 100:00:00.00 -18:11:21.091 DestinationSideOfPier Test skipped as AligmentMode is not German Polar -18:11:22.864 SlewToAltAz INFO Slewed to within 144:49:47.00 DD:MM:SS of expected Azimuth: 150:00:00.00 -18:11:22.880 SlewToAltAz INFO Slewed to within 46:00:01.00 DD:MM:SS of expected Altitude: 50:00:00.00 -18:11:22.920 SlewToAltAz (Bad L) OK Correctly rejected bad Altitude coordinate: -100:00:00.00 -18:11:23.710 SlewToAltAz (Bad L) OK Correctly rejected bad Azimuth coordinate: -10:00:00.00 -18:11:23.771 SlewToAltAz (Bad H) OK Correctly rejected bad Altitude coordinate: 100:00:00.00 -18:11:24.447 SlewToAltAz (Bad H) OK Correctly rejected bad Azimuth coordinate: 370:00:00.00 -18:11:31.196 SlewToAltAzAsync INFO Slewed to within 149:51:53.00 DD:MM:SS of expected Azimuth: 155:00:00.00 -18:11:31.210 SlewToAltAzAsync INFO Slewed to within 51:00:01.00 DD:MM:SS of expected Altitude: 55:00:00.00 -18:11:31.251 SlewToAltAzAsync (Bad L) OK Correctly rejected bad Altitude coordinate: -100:00:00.00 -18:11:32.060 SlewToAltAzAsync (Bad L) OK Correctly rejected bad Azimuth coordinate: -10:00:00.00 -18:11:32.121 SlewToAltAzAsync (Bad H) OK Correctly rejected bad Altitude coordinate: 100:00:00.00 -18:11:32.814 SlewToAltAzAsync (Bad H) OK Correctly rejected bad Azimuth coordinate: 370:00:00.00 -18:11:56.494 SyncToTarget INFO Slewed to start position within 40.1 arc seconds of expected RA: 02:21:18.67, actual RA: 02:21:16.00 -18:11:56.509 SyncToTarget OK Slewed to start position OK. DEC: 00:00:00.00 -18:11:59.005 SyncToTarget INFO Synced to sync position within 55.1 arc seconds of expected RA: 02:17:18.67, actual RA: 02:17:15.00 -18:11:59.019 SyncToTarget INFO Synced to sync position within 3660.0 arc seconds of expected DEC: -01:00:00.00, actual DEC: 00:01:00.00 -18:12:22.398 SyncToTarget INFO Slewed back to start position within 40.1 arc seconds of expected RA: 02:21:18.67, actual RA: 02:21:16.00 -18:12:22.416 SyncToTarget OK Slewed back to start position OK. DEC: 00:00:00.00 -18:12:24.739 SyncToTarget INFO Synced to reversed sync position within 55.1 arc seconds of expected RA: 02:25:18.67, actual RA: 02:25:15.00 -18:12:24.754 SyncToTarget INFO Synced to reversed sync position within 7200.0 arc seconds of expected DEC: 01:00:00.00, actual DEC: -01:00:00.00 -18:12:46.438 SyncToTarget INFO Slewed back to start position within 40.1 arc seconds of expected RA: 02:21:18.67, actual RA: 02:21:16.00 -18:12:46.452 SyncToTarget OK Slewed back to start position OK. DEC: 00:00:00.00 -18:12:46.491 SyncToTarget (Bad L) OK Telescope.TargetRA correctly rejected bad RA coordinate: -01:00:00.00 -18:12:46.643 SyncToTarget (Bad L) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: -100:00:00.00 -18:12:46.702 SyncToTarget (Bad H) OK Telescope.TargetRA correctly rejected bad RA coordinate: 25:00:00.00 -18:12:46.884 SyncToTarget (Bad H) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: 100:00:00.00 -18:12:47.702 SyncToAltAz OK CanSyncAltAz is False and a MethodNotImplementedException exception was generated as expected - -SideOfPier Model Tests -18:12:47.769 SideOfPier Model Tests INFO Tests skipped because this driver does Not support SideOfPier Read - -Post-run Checks -18:12:47.862 Mount Safety INFO Tracking can't be turned off for this mount, please switch off manually. - -Conformance test complete - -No errors, warnings or issues found: your driver passes ASCOM validation!! - -Driver Hash Value: 0C55C8535B9B3A4048581454F9D6A263EFD16E20AEC783762006739F671F586B64F1D8DD3E1E613C27A5B24838AEB8656C2A19BD1EE69177ECF2619599C0DA3C diff --git a/ASCOM.MeadeGeneric.Telescope.Validation.txt b/ASCOM.MeadeGeneric.Telescope.Validation.txt new file mode 100644 index 0000000..bb3467c --- /dev/null +++ b/ASCOM.MeadeGeneric.Telescope.Validation.txt @@ -0,0 +1,269 @@ +Conform Report Hash (V1): D69EDCF187DF1AC5C724D181E8851014862252A6F96F5BD9DDE1C8AF96CB36F7E019D48BA6EF49E5B0990934E09B5898888AF1D957D11C05084347222405B5F1 + + +ConformanceCheck ASCOM Device Conformance Checker Version 6.4.63.0, Build time: 18/12/2018 08:58:32 +ConformanceCheck Running on: ASCOM Platform 6.4 SP1 6.4.1.2695 + +ConformanceCheck Driver ProgID: ASCOM.MeadeGeneric.Telescope + +Error handling +Error number for "Not Implemented" is: 80040400 +Error number for "Invalid Value 1" is: 80040401 +Error number for "Invalid Value 2" is: 80040405 +Error number for "Value Not Set 1" is: 80040402 +Error number for "Value Not Set 2" is: 80040403 +Error messages will not be interpreted to infer state. + +23:26:10.548 Driver Access Checks OK +23:26:11.194 AccessChecks OK Successfully created driver using late binding +23:26:11.406 AccessChecks OK Successfully connected using late binding +23:26:11.412 AccessChecks INFO The driver is a .NET object +23:26:11.417 AccessChecks INFO The AssemblyQualifiedName is: ASCOM.Meade.net.Telescope, ASCOM.Meade.net.Telescope, Version=6.4.0.0, Cultu +23:26:11.422 AccessChecks INFO The driver implements interface: ASCOM.DeviceInterface.ITelescopeV3 +23:26:12.127 AccessChecks INFO Device does not expose interface ITelescopeV2 +23:26:12.921 AccessChecks INFO Device exposes interface ITelescopeV3 +23:26:14.237 AccessChecks OK Successfully created driver using driver access toolkit +23:26:14.396 AccessChecks OK Successfully connected using driver access toolkit + +Conform is using ASCOM.DriverAccess.Telescope to get a Telescope object +23:26:15.721 ConformanceCheck OK Driver instance created successfully +23:26:15.918 ConformanceCheck OK Connected OK + +Common Driver Methods +23:26:15.957 InterfaceVersion OK 3 +23:26:15.986 Connected OK True +23:26:16.014 Description OK Meade Generic +23:26:16.042 DriverInfo OK Information about the driver itself. Version: 6.4 +23:26:16.071 DriverVersion OK 6.4 +23:26:16.099 Name OK Meade Generic +23:26:16.127 CommandString INFO Conform cannot test the CommandString method +23:26:16.133 CommandBlind INFO Conform cannot test the CommandBlind method +23:26:16.140 CommandBool INFO Conform cannot test the CommandBool method +23:26:16.146 Action INFO Conform cannot test the Action method +23:26:16.153 SupportedActions OK Driver returned an empty action list + +Can Properties +23:26:16.220 CanFindHome OK False +23:26:16.227 CanPark OK True +23:26:16.235 CanPulseGuide OK True +23:26:16.242 CanSetDeclinationRate OK False +23:26:16.250 CanSetGuideRates OK False +23:26:16.259 CanSetPark OK False +23:26:16.268 CanSetPierSide OK False +23:26:16.315 CanSetRightAscensionRate OK False +23:26:16.323 CanSetTracking OK True +23:26:16.331 CanSlew OK True +23:26:16.338 CanSlewltAz OK True +23:26:16.347 CanSlewAltAzAsync OK True +23:26:16.355 CanSlewAsync OK True +23:26:16.364 CanSync OK True +23:26:16.372 CanSyncAltAz OK False +23:26:16.381 CanUnPark OK False + +Pre-run Checks +23:26:16.430 Mount Safety INFO Scope is not parked, continuing testing +23:26:16.461 Mount Safety INFO Scope tracking has been enabled +23:26:16.493 TimeCheck INFO PC Time Zone: GMT Summer Time, offset -1 hours. +23:26:16.501 TimeCheck INFO PC UTCDate: 16-May-2019 22:26:16.501 +23:26:20.497 TimeCheck INFO Mount UTCDate: 02-May-2019 20:48:03.000 + +Properties +23:26:20.700 AlignmentMode OK algPolar +23:26:21.594 Altitude WARNING Altitude is <0.0 degrees: -0.69166667 +23:26:21.628 ApertureArea OK Optional member threw a PropertyNotImplementedException exception. +23:26:21.662 ApertureDiameter OK Optional member threw a PropertyNotImplementedException exception. +23:26:21.694 AtHome OK False +23:26:21.726 AtPark OK False +23:26:22.439 Azimuth OK 104.84 +23:26:23.342 Declination OK 00:41:30.00 +23:26:23.374 DeclinationRate Read OK 0.00 +23:26:23.406 DeclinationRate Write OK CanSetDeclinationRate is False and a PropertyNotImplementedException exception was generated as expected +23:26:23.440 DoesRefraction Read OK Optional member threw a PropertyNotImplementedException exception. +23:26:23.474 DoesRefraction Write OK Optional member threw a PropertyNotImplementedException exception. +23:26:23.507 EquatorialSystem OK equLocalTopocentric +23:26:23.539 FocalLength OK Optional member threw a PropertyNotImplementedException exception. +23:26:23.590 GuideRateDeclination Read OK Optional member threw a PropertyNotImplementedException exception. +23:26:23.601 GuideRateDeclination Write OK CanSetGuideRates is False and a PropertyNotImplementedException exception was generated as expected +23:26:23.634 GuideRateRightAscension Read OK Optional member threw a PropertyNotImplementedException exception. +23:26:23.645 GuideRateRightAscension Write OK CanSetGuideRates is False and a PropertyNotImplementedException exception was generated as expected +23:26:23.679 IsPulseGuiding OK False +23:26:24.386 RightAscension OK 09:40:57.00 +23:26:24.420 RightAscensionRate Read OK 0.00 +23:26:24.455 RightAscensionRate Write OK CanSetRightAscensionRate is False and a PropertyNotImplementedException exception was generated as expected +23:26:24.488 SiteElevation Read OK Optional member threw a PropertyNotImplementedException exception. +23:26:24.523 SiteElevation Write OK Optional member threw a PropertyNotImplementedException exception. +23:26:24.534 SiteElevation Write OK Optional member threw a PropertyNotImplementedException exception. +23:26:24.545 SiteElevation Write OK Optional member threw a PropertyNotImplementedException exception. +23:26:25.089 SiteLatitude Read OK 00:00:00.00 +23:26:25.126 SiteLatitude Write OK Invalid Value exception generated as expected on set site latitude < -90 degrees +23:26:25.137 SiteLatitude Write OK Invalid Value exception generated as expected on set site latitude > 90 degrees +23:26:27.021 SiteLatitude Write OK Legal value 00:00:00.00 degrees written successfully +23:26:27.875 SiteLongitude Read OK -53:48:00.00 +23:26:27.911 SiteLongitude Write OK Invalid Value exception generated as expected on set site longitude < -180 degrees +23:26:27.924 SiteLongitude Write OK Invalid Value exception generated as expected on set site longitude > 180 degrees +23:26:29.326 SiteLongitude Write OK Legal value -53:48:00.00 degrees written successfully +23:26:29.945 Slewing OK False +23:26:29.983 SlewSettleTime Read OK Optional member threw a PropertyNotImplementedException exception. +23:26:30.019 SlewSettleTime Write OK Optional member threw a PropertyNotImplementedException exception. +23:26:30.037 SlewSettleTime Write OK Optional member threw a PropertyNotImplementedException exception. +23:26:30.072 SideOfPier Read OK Optional member threw a PropertyNotImplementedException exception. +23:26:30.839 SiderealTime OK 10:27:03.72 +23:26:30.851 SiderealTime OK Scope and ASCOM sidereal times agree to better than 5 minutes, Scope: 10:27:03.72, ASCOM: 10:28:41.54 +23:26:30.888 TargetDeclination Read OK .NET Not InvalidOperationException generated on read before write +23:26:30.923 TargetDeclination Write INFO Tests moved after the SlewToCoordinates tests so that Conform can check they properly set target coordinates. +23:26:30.936 TargetRightAscension Read OK .NET Not InvalidOperationException generated on read before write +23:26:30.975 TargetRightAscension Write INFO Tests moved after the SlewToCoordinates tests so that Conform can check they properly set target coordinates. +23:26:30.988 Tracking Read OK True +23:26:32.051 Tracking Write OK False +23:26:33.116 TrackingRates Found drive rate: driveSidereal +23:26:33.129 TrackingRates Found drive rate: driveLunar +23:26:33.141 TrackingRates OK Drive rates read OK +23:26:33.154 TrackingRates OK Disposed tracking rates OK +23:26:33.189 TrackingRates OK Successfully obtained a TrackingRates object after the previous TrackingRates object was disposed +23:26:33.205 TrackingRate Read OK driveSidereal +23:26:33.244 TrackingRate Write OK Successfully set drive rate: driveSidereal +23:26:33.281 TrackingRate Write OK Successfully set drive rate: driveLunar +23:26:37.575 UTCDate Read OK 02-May-2019 20:48:20.000 +23:26:43.750 UTCDate Write OK New UTCDate written successfully: 02/05/2019 21:48:20 + +Methods +23:26:50.522 CanMoveAxis:Primary OK CanMoveAxis:Primary True +23:26:50.560 CanMoveAxis:Secondary OK CanMoveAxis:Secondary True +23:26:50.597 CanMoveAxis:Tertiary OK CanMoveAxis:Tertiary False +23:26:50.634 Park/Unpark INFO Tests skipped +23:26:50.653 AbortSlew OK AbortSlew OK when not slewing +23:26:50.718 AxisRate:Primary OK Axis rate minimum: 1 Axis rate maximum: 1 +23:26:50.731 AxisRate:Primary OK Axis rate minimum: 2 Axis rate maximum: 2 +23:26:50.743 AxisRate:Primary OK Axis rate minimum: 3 Axis rate maximum: 3 +23:26:50.755 AxisRate:Primary OK Axis rate minimum: 4 Axis rate maximum: 4 +23:26:50.767 AxisRate:Primary OK No overlapping axis rates found +23:26:50.781 AxisRate:Primary OK No duplicate axis rates found +23:26:50.795 AxisRate:Primary OK Successfully disposed of rate 1 - 1 +23:26:50.809 AxisRate:Primary OK Successfully disposed of rate 2 - 2 +23:26:50.823 AxisRate:Primary OK Successfully disposed of rate 3 - 3 +23:26:50.836 AxisRate:Primary OK Successfully disposed of rate 4 - 4 +23:26:50.848 AxisRate:Primary OK Disposed axis rates OK +23:26:50.862 AxisRate:Secondary OK Axis rate minimum: 1 Axis rate maximum: 1 +23:26:50.875 AxisRate:Secondary OK Axis rate minimum: 2 Axis rate maximum: 2 +23:26:50.888 AxisRate:Secondary OK Axis rate minimum: 3 Axis rate maximum: 3 +23:26:50.901 AxisRate:Secondary OK Axis rate minimum: 4 Axis rate maximum: 4 +23:26:50.913 AxisRate:Secondary OK No overlapping axis rates found +23:26:50.926 AxisRate:Secondary OK No duplicate axis rates found +23:26:50.939 AxisRate:Secondary OK Successfully disposed of rate 1 - 1 +23:26:50.951 AxisRate:Secondary OK Successfully disposed of rate 2 - 2 +23:26:50.964 AxisRate:Secondary OK Successfully disposed of rate 3 - 3 +23:26:50.977 AxisRate:Secondary OK Successfully disposed of rate 4 - 4 +23:26:50.991 AxisRate:Secondary OK Disposed axis rates OK +23:26:51.006 AxisRate:Tertiary OK Empty axis rate returned +23:26:51.020 AxisRate:Tertiary OK Disposed axis rates OK +23:26:51.037 FindHome OK CanFindHome is False and a MethodNotImplementedException exception was generated as expected +23:26:51.087 MoveAxis Primary OK Can successfully set a movement rate of zero +23:26:51.104 MoveAxis Primary OK Exception correctly generated when move axis is set below lowest rate (0.5) +23:26:51.141 MoveAxis Primary OK Exception correctly generated when move axis is set above highest rate (5) +23:26:55.308 MoveAxis Primary OK Successfully moved axis at minimum rate: 1 +23:27:01.632 MoveAxis Primary OK Successfully moved axis at maximum rate: 4 +23:27:05.847 MoveAxis Primary OK Tracking state correctly retained for both tracking states +23:27:05.863 MoveAxis Primary OK AxisRates object successfully disposed +23:27:05.923 MoveAxis Secondary OK Can successfully set a movement rate of zero +23:27:05.939 MoveAxis Secondary OK Exception correctly generated when move axis is set below lowest rate (0.5) +23:27:05.976 MoveAxis Secondary OK Exception correctly generated when move axis is set above highest rate (5) +23:27:10.149 MoveAxis Secondary OK Successfully moved axis at minimum rate: 1 +23:27:16.454 MoveAxis Secondary OK Successfully moved axis at maximum rate: 4 +23:27:20.626 MoveAxis Secondary OK Tracking state correctly retained for both tracking states +23:27:20.641 MoveAxis Secondary OK AxisRates object successfully disposed +23:27:20.701 MoveAxis Tertiary OK CanMoveAxis Tertiary is False and a MethodNotImplementedException exception was generated as expected +23:27:22.740 PulseGuide OK Synchronous pulse guide found OK +23:27:53.262 SlewToCoordinates OK Slewed OK. RA: 09:27:56.59 +23:27:53.280 SlewToCoordinates INFO Slewed within 7200.0 arc seconds of expected DEC: 01:00:00.00, actual DEC: -01:00:00.00 +23:27:53.295 SlewToCoordinates OK The TargetRightAscension property 09:27:56.59 matches the expected RA OK. +23:27:53.309 SlewToCoordinates OK The TargetDeclination property 01:00:00.00 matches the expected Declination OK. +23:27:53.356 SlewToCoordinates (Bad L) OK Correctly rejected bad RA coordinate: -01:00:00.00 +23:27:57.509 SlewToCoordinates (Bad L) OK Correctly rejected bad Dec coordinate: -100:00:00.00 +23:27:57.569 SlewToCoordinates (Bad H) OK Correctly rejected bad RA coordinate: 25:00:00.00 +23:28:01.565 SlewToCoordinates (Bad H) OK Correctly rejected bad Dec coordinate: 100:00:00.00 +23:28:33.449 SlewToCoordinatesAsync INFO Slewed within 52.6 arc seconds of expected RA: 08:28:35.51, actual RA: 08:28:32.00 +23:28:33.463 SlewToCoordinatesAsync INFO Slewed within 14398.0 arc seconds of expected DEC: 02:00:00.00, actual DEC: -01:59:58.00 +23:28:33.479 SlewToCoordinatesAsync OK The TargetRightAscension property 08:28:35.51 matches the expected RA OK. +23:28:33.496 SlewToCoordinatesAsync OK The TargetDeclination property 02:00:00.00 matches the expected Declination OK. +23:28:33.539 SlewToCoordinatesAsync (Bad L) OK Correctly rejected bad RA coordinate: -01:00:00.00 +23:28:37.553 SlewToCoordinatesAsync (Bad L) OK Correctly rejected bad Dec coordinate: -100:00:00.00 +23:28:37.614 SlewToCoordinatesAsync (Bad H) OK Correctly rejected bad RA coordinate: 25:00:00.00 +23:28:41.477 SlewToCoordinatesAsync (Bad H) OK Correctly rejected bad Dec coordinate: 100:00:00.00 +23:29:15.070 SyncToCoordinates INFO Slewed to start position within 18.3 arc seconds of expected RA: 07:29:17.22, actual RA: 07:29:16.00 +23:29:15.084 SyncToCoordinates OK Slewed to start position OK. DEC: 00:00:00.00 +23:29:22.898 SyncToCoordinates INFO Synced to sync position within 33.3 arc seconds of expected RA: 07:25:17.22, actual RA: 07:25:15.00 +23:29:22.912 SyncToCoordinates INFO Synced to sync position within 3660.0 arc seconds of expected DEC: -01:00:00.00, actual DEC: 00:01:00.00 +23:29:22.928 SyncToCoordinates OK The TargetRightAscension property 07:25:17.22 matches the expected RA OK. +23:29:22.942 SyncToCoordinates OK The TargetDeclination property -01:00:00.00 matches the expected Declination OK. +23:29:51.132 SyncToCoordinates INFO Slewed back to start position within 18.3 arc seconds of expected RA: 07:29:17.22, actual RA: 07:29:16.00 +23:29:51.146 SyncToCoordinates OK Slewed back to start position OK. DEC: 00:00:00.00 +23:30:01.039 SyncToCoordinates INFO Synced to reversed sync position within 18.3 arc seconds of expected RA: 07:33:17.22, actual RA: 07:33:16.00 +23:30:01.052 SyncToCoordinates INFO Synced to reversed sync position within 7200.0 arc seconds of expected DEC: 01:00:00.00, actual DEC: -01:00:00.00 +23:30:31.303 SyncToCoordinates INFO Slewed back to start position within 18.3 arc seconds of expected RA: 07:29:17.22, actual RA: 07:29:16.00 +23:30:31.317 SyncToCoordinates OK Slewed back to start position OK. DEC: 00:00:00.00 +23:30:31.357 SyncToCoordinates (Bad L) OK Correctly rejected bad RA coordinate: -01:00:00.00 +23:30:35.078 SyncToCoordinates (Bad L) OK Correctly rejected bad Dec coordinate: -100:00:00.00 +23:30:35.139 SyncToCoordinates (Bad H) OK Correctly rejected bad RA coordinate: 25:00:00.00 +23:30:39.202 SyncToCoordinates (Bad H) OK Correctly rejected bad Dec coordinate: 100:00:00.00 +23:30:39.261 TargetRightAscension Write OK Invalid Value exception generated as expected on set TargetRightAscension < 0 hours +23:30:39.277 TargetRightAscension Write OK Invalid Value exception generated as expected on set TargetRightAscension > 24 hours +23:30:43.319 TargetRightAscension Write OK Legal value 06:31:13.60 HH:MM:SS written successfully +23:30:43.356 TargetDeclination Write OK Invalid Value exception generated as expected on set TargetDeclination < -90 degrees +23:30:43.372 TargetDeclination Write OK Invalid Value exception generated as expected on set TargetDeclination < -90 degrees +23:30:46.838 TargetDeclination Write OK Legal value 01:00:00.00 DD:MM:SS written successfully +23:31:17.315 SlewToTarget INFO Slewed within 18.1 arc seconds of expected RA: 07:31:21.21, actual RA: 07:31:20.00 +23:31:17.331 SlewToTarget INFO Slewed within 21597.0 arc seconds of expected DEC: 03:00:00.00, actual DEC: -02:59:57.00 +23:31:17.350 SlewToTarget OK The TargetRightAscension property 07:31:21.21 matches the expected RA OK. +23:31:17.379 SlewToTarget OK The TargetDeclination property 03:00:00.00 matches the expected Declination OK. +23:31:17.421 SlewToTarget (Bad L) OK Telescope.TargetRA correctly rejected bad RA coordinate: -01:00:00.00 +23:31:18.143 SlewToTarget (Bad L) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: -100:00:00.00 +23:31:18.205 SlewToTarget (Bad H) OK Telescope.TargetRA correctly rejected bad RA coordinate: 25:00:00.00 +23:31:18.847 SlewToTarget (Bad H) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: 100:00:00.00 +23:31:51.318 SlewToTargetAsync INFO Slewed within 19.9 arc seconds of expected RA: 06:31:53.33, actual RA: 06:31:52.00 +23:31:51.332 SlewToTargetAsync INFO Slewed within 28799.0 arc seconds of expected DEC: 04:00:00.00, actual DEC: -03:59:59.00 +23:31:51.347 SlewToTargetAsync OK The TargetRightAscension property 06:31:53.33 matches the expected RA OK. +23:31:51.361 SlewToTargetAsync OK The TargetDeclination property 04:00:00.00 matches the expected Declination OK. +23:31:51.402 SlewToTargetAsync (Bad L) OK Telescope.TargetRA correctly rejected bad RA coordinate: -01:00:00.00 +23:31:52.156 SlewToTargetAsync (Bad L) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: -100:00:00.00 +23:31:52.221 SlewToTargetAsync (Bad H) OK Telescope.TargetRA correctly rejected bad RA coordinate: 25:00:00.00 +23:31:52.868 SlewToTargetAsync (Bad H) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: 100:00:00.00 +23:31:52.927 DestinationSideOfPier Test skipped as AligmentMode is not German Polar +23:32:02.721 SlewToAltAz INFO Slewed to within 93:51:10.00 DD:MM:SS of expected Azimuth: 150:00:00.00 +23:32:02.735 SlewToAltAz INFO Slewed to within 46:00:01.00 DD:MM:SS of expected Altitude: 50:00:00.00 +23:32:02.775 SlewToAltAz (Bad L) OK Correctly rejected bad Altitude coordinate: -100:00:00.00 +23:32:06.306 SlewToAltAz (Bad L) OK Correctly rejected bad Azimuth coordinate: -10:00:00.00 +23:32:06.368 SlewToAltAz (Bad H) OK Correctly rejected bad Altitude coordinate: 100:00:00.00 +23:32:09.947 SlewToAltAz (Bad H) OK Correctly rejected bad Azimuth coordinate: 370:00:00.00 +23:32:24.096 SlewToAltAzAsync INFO Slewed to within 98:56:32.00 DD:MM:SS of expected Azimuth: 155:00:00.00 +23:32:24.110 SlewToAltAzAsync INFO Slewed to within 51:00:01.00 DD:MM:SS of expected Altitude: 55:00:00.00 +23:32:24.154 SlewToAltAzAsync (Bad L) OK Correctly rejected bad Altitude coordinate: -100:00:00.00 +23:32:27.733 SlewToAltAzAsync (Bad L) OK Correctly rejected bad Azimuth coordinate: -10:00:00.00 +23:32:27.793 SlewToAltAzAsync (Bad H) OK Correctly rejected bad Altitude coordinate: 100:00:00.00 +23:32:31.369 SlewToAltAzAsync (Bad H) OK Correctly rejected bad Azimuth coordinate: 370:00:00.00 +23:33:05.205 SyncToTarget INFO Slewed to start position within 56.0 arc seconds of expected RA: 07:33:07.73, actual RA: 07:33:04.00 +23:33:05.220 SyncToTarget OK Slewed to start position OK. DEC: 00:00:00.00 +23:33:15.238 SyncToTarget INFO Synced to sync position within 71.0 arc seconds of expected RA: 07:29:07.73, actual RA: 07:29:03.00 +23:33:15.252 SyncToTarget INFO Synced to sync position within 3660.0 arc seconds of expected DEC: -01:00:00.00, actual DEC: 00:01:00.00 +23:33:45.180 SyncToTarget INFO Slewed back to start position within 56.0 arc seconds of expected RA: 07:33:07.73, actual RA: 07:33:04.00 +23:33:45.195 SyncToTarget OK Slewed back to start position OK. DEC: 00:00:00.00 +23:33:55.078 SyncToTarget INFO Synced to reversed sync position within 71.0 arc seconds of expected RA: 07:37:07.73, actual RA: 07:37:03.00 +23:33:55.093 SyncToTarget INFO Synced to reversed sync position within 7200.0 arc seconds of expected DEC: 01:00:00.00, actual DEC: -01:00:00.00 +23:34:25.167 SyncToTarget INFO Slewed back to start position within 56.0 arc seconds of expected RA: 07:33:07.73, actual RA: 07:33:04.00 +23:34:25.192 SyncToTarget OK Slewed back to start position OK. DEC: 00:00:00.00 +23:34:25.232 SyncToTarget (Bad L) OK Telescope.TargetRA correctly rejected bad RA coordinate: -01:00:00.00 +23:34:25.975 SyncToTarget (Bad L) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: -100:00:00.00 +23:34:26.037 SyncToTarget (Bad H) OK Telescope.TargetRA correctly rejected bad RA coordinate: 25:00:00.00 +23:34:26.840 SyncToTarget (Bad H) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: 100:00:00.00 +23:34:30.138 SyncToAltAz OK CanSyncAltAz is False and a MethodNotImplementedException exception was generated as expected + +SideOfPier Model Tests +23:34:30.213 SideOfPier Model Tests INFO Tests skipped because this driver does Not support SideOfPier Read + +Post-run Checks +23:34:30.305 Mount Safety OK Tracking stopped to protect your mount. + +Conformance test complete + +Your driver had 0 errors, 1 warning and 0 issue + +Driver Hash Value: E1CEF5959BFC13BA7F63F73CE38558054C95A93AFE504B722E7EBCDB7DC403E36016216B81EE41CCFE368BC213DDAA8F952A53B3DC88D294D73445C25125D33D diff --git a/ASCOM.MeadeGeneric.focuser.Validation.txt b/ASCOM.MeadeGeneric.focuser.Validation.txt new file mode 100644 index 0000000..d263ba5 --- /dev/null +++ b/ASCOM.MeadeGeneric.focuser.Validation.txt @@ -0,0 +1,69 @@ +Conform Report Hash (V1): 67092198F82F435D10580E3B3E3E460EC0B5E438532A7B5D83C09425222F097CA9A23C883D4238654F217FCA9801E46720811B7CDB982868C68843CCDA33E7FB + + +ConformanceCheck ASCOM Device Conformance Checker Version 6.4.63.0, Build time: 18/12/2018 08:58:32 +ConformanceCheck Running on: ASCOM Platform 6.4 SP1 6.4.1.2695 + +ConformanceCheck Driver ProgID: ASCOM.MeadeGeneric.focuser + +Error handling +Error number for "Not Implemented" is: 80040400 +Error number for "Invalid Value 1" is: 80040404 +Error number for "Value Not Set 1" is: 80040402 +Error number for "Value Not Set 2" is: 80040403 +Error messages will not be interpreted to infer state. + +23:25:52.259 Driver Access Checks OK +23:25:52.911 AccessChecks OK Successfully created driver using late binding +23:25:53.126 AccessChecks OK Successfully connected using late binding +23:25:53.131 AccessChecks INFO The driver is a .NET object +23:25:53.137 AccessChecks INFO The AssemblyQualifiedName is: ASCOM.Meade.net.Focuser, ASCOM.Meade.net.Focuser, Version=6.4.0.0, Culture=n +23:25:53.142 AccessChecks INFO The driver implements interface: ASCOM.DeviceInterface.IFocuserV3 +23:25:53.793 AccessChecks INFO Device does not expose IFocuser interface +23:25:53.855 AccessChecks INFO Device does not expose IFocuserV2 interface +23:25:54.068 AccessChecks INFO Device exposes IFocuserV3 interface +23:25:54.190 AccessChecks OK Successfully created driver using driver access toolkit +23:25:54.344 AccessChecks OK Successfully connected using driver access toolkit +23:25:54.407 AccessChecks OK Successfully disconnected using driver access toolkit + +Conform is using ASCOM.DriverAccess.Focuser to get a Focuser object +23:25:54.480 ConformanceCheck OK Driver instance created successfully +23:25:54.679 ConformanceCheck OK Connected OK + +Common Driver Methods +23:25:54.721 InterfaceVersion OK 3 +23:25:54.750 Connected OK True +23:25:54.779 Description OK Meade Generic +23:25:54.808 DriverInfo OK Information about the driver itself. Version: 6.4 +23:25:54.837 DriverVersion OK 6.4 +23:25:54.866 Name OK Meade Generic +23:25:54.895 CommandString INFO Conform cannot test the CommandString method +23:25:54.901 CommandBlind INFO Conform cannot test the CommandBlind method +23:25:54.908 CommandBool INFO Conform cannot test the CommandBool method +23:25:54.916 Action INFO Conform cannot test the Action method +23:25:54.924 SupportedActions OK Driver returned an empty action list + +Properties +23:25:55.035 Absolute OK False +23:25:55.043 IsMoving OK False +23:25:55.051 MaxStep OK 7000 +23:25:55.059 MaxIncrement OK 7000 +23:25:55.073 Position OK Position must not be implemented for a relative focuser and a PropertyNotImplementedException exception was generated as expected +23:25:55.084 StepSize OK Optional member threw a PropertyNotImplementedException exception. +23:25:55.091 TempCompAvailable OK False +23:25:55.100 TempComp Read OK False +23:25:55.108 TempComp Write OK Temperature compensation is not available and a PropertyNotImplementedException exception was generated as expected +23:25:55.120 Temperature OK Optional member threw a PropertyNotImplementedException exception. + +Methods +23:25:55.170 Halt OK Focuser halted OK +23:25:55.183 Move - TempComp False Moving by: 700 +23:25:55.895 Move - TempComp False Asynchronous move found +23:25:55.904 Move - TempComp False OK Relative move OK +23:25:55.915 Move - TempComp False INFO Returning to original position: 0 + +Conformance test complete + +No errors, warnings or issues found: your driver passes ASCOM validation!! + +Driver Hash Value: DE237CD97B3A95A514A5D7FD9B265F5F98174E67A28B44FF5CA2FC9A1CF355DA2C4DFB9416F3C40A8056334CC5339F0D8F8A39026A4BB5E3AE1FFC2BD8F55FBD diff --git a/ConformanceResult.txt b/ConformanceResult.txt deleted file mode 100644 index beecf54..0000000 --- a/ConformanceResult.txt +++ /dev/null @@ -1,272 +0,0 @@ -Start-up ASCOM Device Conformance Checker - 64bit mode -Start-up ASCOM Platform 6.4 SP1 6.4.1.2695 - - -ConformanceCheck ASCOM Device Conformance Checker Version 6.4.63.0, Build time: 18/12/2018 08:58:34 -ConformanceCheck Running on: ASCOM Platform 6.4 SP1 6.4.1.2695 - -ConformanceCheck Driver ProgID: ASCOM.MeadeAutostar497.Telescope - -Error handling -Error number for "Not Implemented" is: 80040400 -Error number for "Invalid Value 1" is: 80040401 -Error number for "Invalid Value 2" is: 80040405 -Error number for "Value Not Set 1" is: 80040402 -Error number for "Value Not Set 2" is: 80040403 -Error messages will not be interpreted to infer state. - -18:07:45.625 Driver Access Checks OK -18:07:46.272 AccessChecks OK Successfully created driver using late binding -18:07:46.685 AccessChecks OK Successfully connected using late binding -18:07:46.689 AccessChecks INFO The driver is a .NET object -18:07:46.693 AccessChecks INFO The AssemblyQualifiedName is: ASCOM.MeadeAutostar497.Telescope, ASCOM.MeadeAutostar497.Telescope, Version= -18:07:46.697 AccessChecks INFO The driver implements interface: ASCOM.DeviceInterface.ITelescopeV3 -18:07:46.701 AccessChecks INFO The driver implements interface: ASCOM.DeviceInterface.IFocuserV3 -18:07:47.416 AccessChecks INFO Device does not expose interface ITelescopeV2 -18:07:48.387 AccessChecks INFO Device exposes interface ITelescopeV3 -18:07:49.708 AccessChecks OK Successfully created driver using driver access toolkit -18:07:50.029 AccessChecks OK Successfully connected using driver access toolkit - -Conform is using ASCOM.DriverAccess.Telescope to get a Telescope object -18:07:51.351 ConformanceCheck OK Driver instance created successfully -18:07:51.775 ConformanceCheck OK Connected OK - -Common Driver Methods -18:07:51.817 InterfaceVersion OK 3 -18:07:51.845 Connected OK True -18:07:51.874 Description OK Meade Autostar 497 .net -18:07:51.903 DriverInfo OK Information about the driver itself. Version: 0.0 -18:07:51.932 DriverVersion OK 0.0 -18:07:51.961 Name OK Meade Autostar 497 .net -18:07:51.990 CommandString INFO Conform cannot test the CommandString method -18:07:51.996 CommandBlind INFO Conform cannot test the CommandBlind method -18:07:52.002 CommandBool INFO Conform cannot test the CommandBool method -18:07:52.008 Action INFO Conform cannot test the Action method -18:07:52.015 SupportedActions OK Driver returned an empty action list - -Can Properties -18:07:52.082 CanFindHome OK False -18:07:52.089 CanPark OK True -18:07:52.096 CanPulseGuide OK True -18:07:52.102 CanSetDeclinationRate OK False -18:07:52.109 CanSetGuideRates OK False -18:07:52.117 CanSetPark OK False -18:07:52.125 CanSetPierSide OK False -18:07:52.177 CanSetRightAscensionRate OK False -18:07:52.185 CanSetTracking OK False -18:07:52.193 CanSlew OK True -18:07:52.200 CanSlewltAz OK True -18:07:52.208 CanSlewAltAzAsync OK True -18:07:52.216 CanSlewAsync OK True -18:07:52.224 CanSync OK True -18:07:52.231 CanSyncAltAz OK False -18:07:52.239 CanUnPark OK False - -Pre-run Checks -18:07:52.286 Mount Safety INFO Scope is not parked, continuing testing -18:07:52.339 TimeCheck INFO PC Time Zone: GMT Summer Time, offset -1 hours. -18:07:52.348 TimeCheck INFO PC UTCDate: 08-May-2019 17:07:52.347 -18:07:53.254 TimeCheck INFO Mount UTCDate: 02-May-2019 19:33:55.000 - -Properties -18:07:53.358 AlignmentMode OK algPolar -18:07:53.515 Altitude OK 1.00 -18:07:53.555 ApertureArea OK Optional member threw a PropertyNotImplementedException exception. -18:07:53.591 ApertureDiameter OK Optional member threw a PropertyNotImplementedException exception. -18:07:53.622 AtHome OK False -18:07:53.654 AtPark OK False -18:07:53.847 Azimuth OK 45.67 -18:07:54.028 Declination OK -01:00:01.00 -18:07:54.060 DeclinationRate Read OK 0.00 -18:07:54.093 DeclinationRate Write OK CanSetDeclinationRate is False and a PropertyNotImplementedException exception was generated as expected -18:07:54.126 DoesRefraction Read OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.159 DoesRefraction Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.193 EquatorialSystem OK equLocalTopocentric -18:07:54.227 FocalLength OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.260 GuideRateDeclination Read OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.270 GuideRateDeclination Write OK CanSetGuideRates is False and a PropertyNotImplementedException exception was generated as expected -18:07:54.303 GuideRateRightAscension Read OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.314 GuideRateRightAscension Write OK CanSetGuideRates is False and a PropertyNotImplementedException exception was generated as expected -18:07:54.347 IsPulseGuiding OK False -18:07:54.541 RightAscension OK 03:59:09.00 -18:07:54.575 RightAscensionRate Read OK 0.00 -18:07:54.609 RightAscensionRate Write OK CanSetRightAscensionRate is False and a PropertyNotImplementedException exception was generated as expected -18:07:54.644 SiteElevation Read OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.678 SiteElevation Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.691 SiteElevation Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.703 SiteElevation Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:54.861 SiteLatitude Read OK 00:00:00.00 -18:07:54.900 SiteLatitude Write OK Invalid Value exception generated as expected on set site latitude < -90 degrees -18:07:54.912 SiteLatitude Write OK Invalid Value exception generated as expected on set site latitude > 90 degrees -18:07:55.315 SiteLatitude Write OK Legal value 00:00:00.00 degrees written successfully -18:07:55.455 SiteLongitude Read OK -42:12:00.00 -18:07:55.490 SiteLongitude Write OK Invalid Value exception generated as expected on set site longitude < -180 degrees -18:07:55.502 SiteLongitude Write OK Invalid Value exception generated as expected on set site longitude > 180 degrees -18:07:56.090 SiteLongitude Write OK Legal value -42:12:00.00 degrees written successfully -18:07:56.246 Slewing OK False -18:07:56.280 SlewSettleTime Read OK Optional member threw a PropertyNotImplementedException exception. -18:07:56.315 SlewSettleTime Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:56.328 SlewSettleTime Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:56.363 SideOfPier Read OK Optional member threw a PropertyNotImplementedException exception. -18:07:56.578 SiderealTime OK 05:17:41.24 -18:07:56.590 SiderealTime INFO Scope and ASCOM sidereal times are up to 0.5 hour different, Scope: 05:17:41.24, ASCOM: 05:24:06.50 -18:07:56.626 TargetDeclination Read OK .NET Not InvalidOperationException generated on read before write -18:07:56.661 TargetDeclination Write INFO Tests moved after the SlewToCoordinates tests so that Conform can check they properly set target coordinates. -18:07:56.673 TargetRightAscension Read OK .NET Not InvalidOperationException generated on read before write -18:07:56.708 TargetRightAscension Write INFO Tests moved after the SlewToCoordinates tests so that Conform can check they properly set target coordinates. -18:07:56.720 Tracking Read OK True -18:07:56.756 Tracking Write OK CanSetTracking is False and a PropertyNotImplementedException exception was generated as expected -18:07:56.797 TrackingRates Found drive rate: driveSidereal -18:07:56.809 TrackingRates OK Drive rates read OK -18:07:56.822 TrackingRates OK Disposed tracking rates OK -18:07:56.858 TrackingRates OK Successfully obtained a TrackingRates object after the previous TrackingRates object was disposed -18:07:57.039 TrackingRate Read OK driveLunar -18:07:57.075 TrackingRate Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:57.088 TrackingRate Write OK Optional member threw a PropertyNotImplementedException exception. -18:07:57.624 UTCDate Read OK 02-May-2019 19:33:59.000 -18:07:58.626 UTCDate Write OK New UTCDate written successfully: 02/05/2019 20:33:59 - -Methods -18:07:59.997 CanMoveAxis:Primary OK CanMoveAxis:Primary True -18:08:00.035 CanMoveAxis:Secondary OK CanMoveAxis:Secondary True -18:08:00.072 CanMoveAxis:Tertiary OK CanMoveAxis:Tertiary False -18:08:00.108 Park/Unpark INFO Tests skipped -18:08:00.131 AbortSlew OK AbortSlew OK when not slewing -18:08:00.199 AxisRate:Primary OK Axis rate minimum: 1 Axis rate maximum: 1 -18:08:00.211 AxisRate:Primary OK Axis rate minimum: 2 Axis rate maximum: 2 -18:08:00.225 AxisRate:Primary OK Axis rate minimum: 3 Axis rate maximum: 3 -18:08:00.239 AxisRate:Primary OK Axis rate minimum: 4 Axis rate maximum: 4 -18:08:00.253 AxisRate:Primary OK No overlapping axis rates found -18:08:00.266 AxisRate:Primary OK No duplicate axis rates found -18:08:00.279 AxisRate:Primary OK Successfully disposed of rate 1 - 1 -18:08:00.295 AxisRate:Primary OK Successfully disposed of rate 2 - 2 -18:08:00.310 AxisRate:Primary OK Successfully disposed of rate 3 - 3 -18:08:00.322 AxisRate:Primary OK Successfully disposed of rate 4 - 4 -18:08:00.336 AxisRate:Primary OK Disposed axis rates OK -18:08:00.350 AxisRate:Secondary OK Axis rate minimum: 1 Axis rate maximum: 1 -18:08:00.362 AxisRate:Secondary OK Axis rate minimum: 2 Axis rate maximum: 2 -18:08:00.375 AxisRate:Secondary OK Axis rate minimum: 3 Axis rate maximum: 3 -18:08:00.387 AxisRate:Secondary OK Axis rate minimum: 4 Axis rate maximum: 4 -18:08:00.400 AxisRate:Secondary OK No overlapping axis rates found -18:08:00.414 AxisRate:Secondary OK No duplicate axis rates found -18:08:00.445 AxisRate:Secondary OK Successfully disposed of rate 1 - 1 -18:08:00.458 AxisRate:Secondary OK Successfully disposed of rate 2 - 2 -18:08:00.472 AxisRate:Secondary OK Successfully disposed of rate 3 - 3 -18:08:00.487 AxisRate:Secondary OK Successfully disposed of rate 4 - 4 -18:08:00.501 AxisRate:Secondary OK Disposed axis rates OK -18:08:00.517 AxisRate:Tertiary OK Empty axis rate returned -18:08:00.531 AxisRate:Tertiary OK Disposed axis rates OK -18:08:00.550 FindHome OK CanFindHome is False and a MethodNotImplementedException exception was generated as expected -18:08:00.606 MoveAxis Primary OK Can successfully set a movement rate of zero -18:08:00.622 MoveAxis Primary OK Exception correctly generated when move axis is set below lowest rate (0.5) -18:08:00.659 MoveAxis Primary OK Exception correctly generated when move axis is set above highest rate (5) -18:08:04.867 MoveAxis Primary OK Successfully moved axis at minimum rate: 1 -18:08:09.770 MoveAxis Primary OK Successfully moved axis at maximum rate: 4 -18:08:13.981 MoveAxis Primary OK Tracking state correctly restored after MoveAxis when CanSetTracking is false -18:08:13.998 MoveAxis Primary OK AxisRates object successfully disposed -18:08:14.057 MoveAxis Secondary OK Can successfully set a movement rate of zero -18:08:14.072 MoveAxis Secondary OK Exception correctly generated when move axis is set below lowest rate (0.5) -18:08:14.107 MoveAxis Secondary OK Exception correctly generated when move axis is set above highest rate (5) -18:08:18.317 MoveAxis Secondary OK Successfully moved axis at minimum rate: 1 -18:08:23.262 MoveAxis Secondary OK Successfully moved axis at maximum rate: 4 -18:08:27.473 MoveAxis Secondary OK Tracking state correctly restored after MoveAxis when CanSetTracking is false -18:08:27.492 MoveAxis Secondary OK AxisRates object successfully disposed -18:08:27.551 MoveAxis Tertiary OK CanMoveAxis Tertiary is False and a MethodNotImplementedException exception was generated as expected -18:08:29.592 PulseGuide OK Synchronous pulse guide found OK -18:08:54.449 SlewToCoordinates INFO Slewed within 39.2 arc seconds of expected RA: 04:18:14.61, actual RA: 04:18:12.00 -18:08:54.463 SlewToCoordinates INFO Slewed within 7199.0 arc seconds of expected DEC: 01:00:00.00, actual DEC: -00:59:59.00 -18:08:54.478 SlewToCoordinates OK The TargetRightAscension property 04:18:14.61 matches the expected RA OK. -18:08:54.492 SlewToCoordinates OK The TargetDeclination property 01:00:00.00 matches the expected Declination OK. -18:08:54.548 SlewToCoordinates (Bad L) OK Correctly rejected bad RA coordinate: -01:00:00.00 -18:08:55.301 SlewToCoordinates (Bad L) OK Correctly rejected bad Dec coordinate: -100:00:00.00 -18:08:55.360 SlewToCoordinates (Bad H) OK Correctly rejected bad RA coordinate: 25:00:00.00 -18:08:56.199 SlewToCoordinates (Bad H) OK Correctly rejected bad Dec coordinate: 100:00:00.00 -18:09:20.477 SlewToCoordinatesAsync INFO Slewed within 19.3 arc seconds of expected RA: 03:18:41.28, actual RA: 03:18:40.00 -18:09:20.490 SlewToCoordinatesAsync INFO Slewed within 14398.0 arc seconds of expected DEC: 02:00:00.00, actual DEC: -01:59:58.00 -18:09:20.503 SlewToCoordinatesAsync OK The TargetRightAscension property 03:18:41.28 matches the expected RA OK. -18:09:20.516 SlewToCoordinatesAsync OK The TargetDeclination property 02:00:00.00 matches the expected Declination OK. -18:09:20.558 SlewToCoordinatesAsync (Bad L) OK Correctly rejected bad RA coordinate: -01:00:00.00 -18:09:21.381 SlewToCoordinatesAsync (Bad L) OK Correctly rejected bad Dec coordinate: -100:00:00.00 -18:09:21.442 SlewToCoordinatesAsync (Bad H) OK Correctly rejected bad RA coordinate: 25:00:00.00 -18:09:22.292 SlewToCoordinatesAsync (Bad H) OK Correctly rejected bad Dec coordinate: 100:00:00.00 -18:09:44.520 SyncToCoordinates INFO Slewed to start position within 56.8 arc seconds of expected RA: 02:19:07.79, actual RA: 02:19:04.00 -18:09:44.534 SyncToCoordinates OK Slewed to start position OK. DEC: 00:00:00.00 -18:09:47.045 SyncToCoordinates INFO Synced to sync position within 71.8 arc seconds of expected RA: 02:15:07.79, actual RA: 02:15:03.00 -18:09:47.058 SyncToCoordinates INFO Synced to sync position within 3660.0 arc seconds of expected DEC: -01:00:00.00, actual DEC: 00:01:00.00 -18:09:47.071 SyncToCoordinates OK The TargetRightAscension property 02:15:07.79 matches the expected RA OK. -18:09:47.085 SyncToCoordinates OK The TargetDeclination property -01:00:00.00 matches the expected Declination OK. -18:10:08.444 SyncToCoordinates INFO Slewed back to start position within 56.8 arc seconds of expected RA: 02:19:07.79, actual RA: 02:19:04.00 -18:10:08.458 SyncToCoordinates OK Slewed back to start position OK. DEC: 00:00:00.00 -18:10:10.791 SyncToCoordinates INFO Synced to reversed sync position within 71.8 arc seconds of expected RA: 02:23:07.79, actual RA: 02:23:03.00 -18:10:10.806 SyncToCoordinates INFO Synced to reversed sync position within 7200.0 arc seconds of expected DEC: 01:00:00.00, actual DEC: -01:00:00.00 -18:10:32.484 SyncToCoordinates INFO Slewed back to start position within 56.8 arc seconds of expected RA: 02:19:07.79, actual RA: 02:19:04.00 -18:10:32.498 SyncToCoordinates OK Slewed back to start position OK. DEC: 00:00:00.00 -18:10:32.537 SyncToCoordinates (Bad L) OK Correctly rejected bad RA coordinate: -01:00:00.00 -18:10:33.329 SyncToCoordinates (Bad L) OK Correctly rejected bad Dec coordinate: -100:00:00.00 -18:10:33.389 SyncToCoordinates (Bad H) OK Correctly rejected bad RA coordinate: 25:00:00.00 -18:10:34.242 SyncToCoordinates (Bad H) OK Correctly rejected bad Dec coordinate: 100:00:00.00 -18:10:34.301 TargetRightAscension Write OK Invalid Value exception generated as expected on set TargetRightAscension < 0 hours -18:10:34.315 TargetRightAscension Write OK Invalid Value exception generated as expected on set TargetRightAscension > 24 hours -18:10:34.979 TargetRightAscension Write OK Legal value 01:20:19.62 HH:MM:SS written successfully -18:10:35.016 TargetDeclination Write OK Invalid Value exception generated as expected on set TargetDeclination < -90 degrees -18:10:35.032 TargetDeclination Write OK Invalid Value exception generated as expected on set TargetDeclination < -90 degrees -18:10:35.652 TargetDeclination Write OK Legal value 01:00:00.00 DD:MM:SS written successfully -18:10:56.417 SlewToTarget INFO Slewed within 14.8 arc seconds of expected RA: 02:20:20.99, actual RA: 02:20:20.00 -18:10:56.434 SlewToTarget INFO Slewed within 21596.0 arc seconds of expected DEC: 03:00:00.00, actual DEC: -02:59:56.00 -18:10:56.449 SlewToTarget OK The TargetRightAscension property 02:20:20.99 matches the expected RA OK. -18:10:56.463 SlewToTarget OK The TargetDeclination property 03:00:00.00 matches the expected Declination OK. -18:10:56.504 SlewToTarget (Bad L) OK Telescope.TargetRA correctly rejected bad RA coordinate: -01:00:00.00 -18:10:56.676 SlewToTarget (Bad L) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: -100:00:00.00 -18:10:56.736 SlewToTarget (Bad H) OK Telescope.TargetRA correctly rejected bad RA coordinate: 25:00:00.00 -18:10:56.915 SlewToTarget (Bad H) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: 100:00:00.00 -18:11:20.512 SlewToTargetAsync INFO Slewed within 35.1 arc seconds of expected RA: 01:20:42.34, actual RA: 01:20:40.00 -18:11:20.526 SlewToTargetAsync INFO Slewed within 28797.0 arc seconds of expected DEC: 04:00:00.00, actual DEC: -03:59:57.00 -18:11:20.539 SlewToTargetAsync OK The TargetRightAscension property 01:20:42.34 matches the expected RA OK. -18:11:20.553 SlewToTargetAsync OK The TargetDeclination property 04:00:00.00 matches the expected Declination OK. -18:11:20.593 SlewToTargetAsync (Bad L) OK Telescope.TargetRA correctly rejected bad RA coordinate: -01:00:00.00 -18:11:20.774 SlewToTargetAsync (Bad L) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: -100:00:00.00 -18:11:20.833 SlewToTargetAsync (Bad H) OK Telescope.TargetRA correctly rejected bad RA coordinate: 25:00:00.00 -18:11:21.032 SlewToTargetAsync (Bad H) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: 100:00:00.00 -18:11:21.091 DestinationSideOfPier Test skipped as AligmentMode is not German Polar -18:11:22.864 SlewToAltAz INFO Slewed to within 144:49:47.00 DD:MM:SS of expected Azimuth: 150:00:00.00 -18:11:22.880 SlewToAltAz INFO Slewed to within 46:00:01.00 DD:MM:SS of expected Altitude: 50:00:00.00 -18:11:22.920 SlewToAltAz (Bad L) OK Correctly rejected bad Altitude coordinate: -100:00:00.00 -18:11:23.710 SlewToAltAz (Bad L) OK Correctly rejected bad Azimuth coordinate: -10:00:00.00 -18:11:23.771 SlewToAltAz (Bad H) OK Correctly rejected bad Altitude coordinate: 100:00:00.00 -18:11:24.447 SlewToAltAz (Bad H) OK Correctly rejected bad Azimuth coordinate: 370:00:00.00 -18:11:31.196 SlewToAltAzAsync INFO Slewed to within 149:51:53.00 DD:MM:SS of expected Azimuth: 155:00:00.00 -18:11:31.210 SlewToAltAzAsync INFO Slewed to within 51:00:01.00 DD:MM:SS of expected Altitude: 55:00:00.00 -18:11:31.251 SlewToAltAzAsync (Bad L) OK Correctly rejected bad Altitude coordinate: -100:00:00.00 -18:11:32.060 SlewToAltAzAsync (Bad L) OK Correctly rejected bad Azimuth coordinate: -10:00:00.00 -18:11:32.121 SlewToAltAzAsync (Bad H) OK Correctly rejected bad Altitude coordinate: 100:00:00.00 -18:11:32.814 SlewToAltAzAsync (Bad H) OK Correctly rejected bad Azimuth coordinate: 370:00:00.00 -18:11:56.494 SyncToTarget INFO Slewed to start position within 40.1 arc seconds of expected RA: 02:21:18.67, actual RA: 02:21:16.00 -18:11:56.509 SyncToTarget OK Slewed to start position OK. DEC: 00:00:00.00 -18:11:59.005 SyncToTarget INFO Synced to sync position within 55.1 arc seconds of expected RA: 02:17:18.67, actual RA: 02:17:15.00 -18:11:59.019 SyncToTarget INFO Synced to sync position within 3660.0 arc seconds of expected DEC: -01:00:00.00, actual DEC: 00:01:00.00 -18:12:22.398 SyncToTarget INFO Slewed back to start position within 40.1 arc seconds of expected RA: 02:21:18.67, actual RA: 02:21:16.00 -18:12:22.416 SyncToTarget OK Slewed back to start position OK. DEC: 00:00:00.00 -18:12:24.739 SyncToTarget INFO Synced to reversed sync position within 55.1 arc seconds of expected RA: 02:25:18.67, actual RA: 02:25:15.00 -18:12:24.754 SyncToTarget INFO Synced to reversed sync position within 7200.0 arc seconds of expected DEC: 01:00:00.00, actual DEC: -01:00:00.00 -18:12:46.438 SyncToTarget INFO Slewed back to start position within 40.1 arc seconds of expected RA: 02:21:18.67, actual RA: 02:21:16.00 -18:12:46.452 SyncToTarget OK Slewed back to start position OK. DEC: 00:00:00.00 -18:12:46.491 SyncToTarget (Bad L) OK Telescope.TargetRA correctly rejected bad RA coordinate: -01:00:00.00 -18:12:46.643 SyncToTarget (Bad L) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: -100:00:00.00 -18:12:46.702 SyncToTarget (Bad H) OK Telescope.TargetRA correctly rejected bad RA coordinate: 25:00:00.00 -18:12:46.884 SyncToTarget (Bad H) OK Telescope.TargetDeclination correctly rejected bad Dec coordinate: 100:00:00.00 -18:12:47.702 SyncToAltAz OK CanSyncAltAz is False and a MethodNotImplementedException exception was generated as expected - -SideOfPier Model Tests -18:12:47.769 SideOfPier Model Tests INFO Tests skipped because this driver does Not support SideOfPier Read - -Post-run Checks -18:12:47.862 Mount Safety INFO Tracking can't be turned off for this mount, please switch off manually. - -Conformance test complete - -No errors, warnings or issues found: your driver passes ASCOM validation!! - -Driver Hash Value: 0C55C8535B9B3A4048581454F9D6A263EFD16E20AEC783762006739F671F586B64F1D8DD3E1E613C27A5B24838AEB8656C2A19BD1EE69177ECF2619599C0DA3C -Report Hash Value: 671D83C15427DD14D9FA7F5A6C62D6B21372362C5173A2B95171D14D7442358DCF3950481797D63EB94E642D2563C3094C387C23D65466833FEA5E64CD045B46 - -The validation file is: C:\Users\colin\Documents\ASCOM\Logs 2019-05-08\ASCOM.MeadeAutostar497.Telescope.Validation.txt diff --git a/FocuserTestConsole/FocuserTestConsole.csproj b/FocuserTestConsole/FocuserTestConsole.csproj new file mode 100644 index 0000000..1067b3a --- /dev/null +++ b/FocuserTestConsole/FocuserTestConsole.csproj @@ -0,0 +1,64 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {AABC96B8-C462-4B3A-9B5F-2929E3CB7A49} + Exe + Properties + ASCOM.MeadeGeneric + ASCOM.MeadeGeneric.Test + v4.7.1 + + + 512 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FocuserTestConsole/Program.cs b/FocuserTestConsole/Program.cs new file mode 100644 index 0000000..af644c8 --- /dev/null +++ b/FocuserTestConsole/Program.cs @@ -0,0 +1,55 @@ +// This implements a console application that can be used to test an ASCOM driver +// + +// This is used to define code in the template that is specific to one class implementation +// unused code can be deleted and this definition removed. + +#define Telescope +// remove this to bypass the code that uses the chooser to select the driver +#define UseChooser + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using ASCOM.DriverAccess; + +namespace ASCOM +{ + class Program + { + static void Main(string[] args) + { + // Uncomment the code that's required +#if UseChooser + // choose the device + string id = ASCOM.DriverAccess.Focuser.Choose("ASCOM.MeadeGeneric.Focuser"); + if (string.IsNullOrEmpty(id)) + return; + // create this device + ASCOM.DriverAccess.Focuser device = new ASCOM.DriverAccess.Focuser(id); +#else + // this can be replaced by this code, it avoids the chooser and creates the driver class directly. + ASCOM.DriverAccess.Telescope device = new ASCOM.DriverAccess.Telescope("ASCOM.MeadeGeneric.Telescope"); +#endif + // now run some tests, adding code to your driver so that the tests will pass. + // these first tests are common to all drivers. + Console.WriteLine("name " + device.Name); + Console.WriteLine("description " + device.Description); + Console.WriteLine("DriverInfo " + device.DriverInfo); + Console.WriteLine("driverVersion " + device.DriverVersion); + + // TODO add more code to test the driver. + device.Connected = true; + + device.Move(2000); + Thread.Sleep(2000); + device.Move(-2000); + + device.Connected = false; + Console.WriteLine("Press Enter to finish"); + Console.ReadLine(); + } + } +} diff --git a/MeadeAutostar497.UnitTests/Properties/AssemblyInfo.cs b/FocuserTestConsole/Properties/AssemblyInfo.cs similarity index 68% rename from MeadeAutostar497.UnitTests/Properties/AssemblyInfo.cs rename to FocuserTestConsole/Properties/AssemblyInfo.cs index ef108f2..5100d4b 100644 --- a/MeadeAutostar497.UnitTests/Properties/AssemblyInfo.cs +++ b/FocuserTestConsole/Properties/AssemblyInfo.cs @@ -2,35 +2,35 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("MeadeAutostar497.UnitTests")] +[assembly: AssemblyTitle("MeadeGeneric Test Application")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("MeadeAutostar497.UnitTests")] -[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyCompany("ASCOM Initiative")] +[assembly: AssemblyProduct("MeadeGeneric")] +[assembly: AssemblyCopyright("Copyright © ASCOM Initiative 2014")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9638da27-77c7-4b30-a730-6e7159a4a09f")] +[assembly: Guid("c7008f94-e3b9-4481-b720-3b56557860c6")] // Version information for an assembly consists of the following four values: // // Major Version -// Minor Version +// Minor Version // Build Number // Revision // -// You can specify all the values or you can default the Build and Revision Numbers +// You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: AssemblyVersion("6.4.0.0")] +[assembly: AssemblyFileVersion("6.4.0.0")] diff --git a/TestConsole/app.config b/FocuserTestConsole/app.config similarity index 71% rename from TestConsole/app.config rename to FocuserTestConsole/app.config index 8935d57..70dcdba 100644 --- a/TestConsole/app.config +++ b/FocuserTestConsole/app.config @@ -1,3 +1,3 @@ - + diff --git a/MeadeAutostar497/ASCOM.ico b/Meade.net.Telescope/ASCOM.ico similarity index 100% rename from MeadeAutostar497/ASCOM.ico rename to Meade.net.Telescope/ASCOM.ico diff --git a/MeadeAutostar497/ASCOM.png b/Meade.net.Telescope/ASCOM.png similarity index 100% rename from MeadeAutostar497/ASCOM.png rename to Meade.net.Telescope/ASCOM.png diff --git a/MeadeAutostar497/ASCOMDriverTemplate.snk b/Meade.net.Telescope/ASCOMDriverTemplate.snk similarity index 100% rename from MeadeAutostar497/ASCOMDriverTemplate.snk rename to Meade.net.Telescope/ASCOMDriverTemplate.snk diff --git a/MeadeAutostar497/MeadeAutostar497.csproj b/Meade.net.Telescope/Meade.net.Telescope.csproj similarity index 81% rename from MeadeAutostar497/MeadeAutostar497.csproj rename to Meade.net.Telescope/Meade.net.Telescope.csproj index fe89ffa..dbf6b21 100644 --- a/MeadeAutostar497/MeadeAutostar497.csproj +++ b/Meade.net.Telescope/Meade.net.Telescope.csproj @@ -8,14 +8,14 @@ {64308775-BD4A-469C-BCAB-3ED830B811AF} Library Properties - ASCOM.MeadeAutostar497 - ASCOM.MeadeAutostar497.Telescope + ASCOM.Meade.net + ASCOM.Meade.net.Telescope 3.5 - v4.6.2 + v4.7.1 ASCOM.ico true ASCOMDriverTemplate.snk @@ -41,18 +41,18 @@ true full false - bin\Debug\ + ..\bin\Debug\ DEBUG;TRACE prompt 4 true - AnyCPU + x86 false pdbonly true - bin\Release\ + ..\bin\Release\ TRACE prompt 4 @@ -69,7 +69,6 @@ - @@ -78,23 +77,12 @@ - - - - - - - - - - - - + True @@ -106,13 +94,7 @@ True Settings.settings - - - Form - - - SetupDialogForm.cs - + @@ -120,10 +102,6 @@ ResXFileCodeGenerator Resources.Designer.cs - - SetupDialogForm.cs - Designer - @@ -154,7 +132,12 @@ true - + + + {3689a2cb-94c5-4012-a5cf-7e7d1dd27143} + Meade.net + + diff --git a/MeadeAutostar497/Properties/AssemblyInfo.cs b/Meade.net.Telescope/Properties/AssemblyInfo.cs similarity index 75% rename from MeadeAutostar497/Properties/AssemblyInfo.cs rename to Meade.net.Telescope/Properties/AssemblyInfo.cs index 495f3d8..e395e18 100644 --- a/MeadeAutostar497/Properties/AssemblyInfo.cs +++ b/Meade.net.Telescope/Properties/AssemblyInfo.cs @@ -7,11 +7,11 @@ using System.Runtime.InteropServices; // associated with an assembly. // // TODO - Add your authorship information here -[assembly: AssemblyTitle("ASCOM.MeadeAutostar497.Telescope")] -[assembly: AssemblyDescription("ASCOM MeadeAutostar497 .net")] +[assembly: AssemblyTitle("ASCOM.Meade.net.Telescope")] +[assembly: AssemblyDescription("ASCOM Telescope driver for Meade.net")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Cjdawson.com")] -[assembly: AssemblyProduct("ASCOM Telescope driver for MeadeAutostar497")] +[assembly: AssemblyCompany("cjdawson.com")] +[assembly: AssemblyProduct("ASCOM Telescope driver for Meade.net")] [assembly: AssemblyCopyright("Copyright © 2019 cjdawson.com")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -22,7 +22,7 @@ using System.Runtime.InteropServices; [assembly: ComVisible(true)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("55d0f255-cb49-4e6b-bc32-4f8fb874734d")] +[assembly: Guid("8b9fccb9-87ae-42f7-90af-079e13de6efb")] // Version information for an assembly consists of the following four values: // @@ -35,5 +35,5 @@ using System.Runtime.InteropServices; // by using the '*' as shown below: // // TODO - Set your driver's version here -[assembly: AssemblyVersion("0.2.0.0")] -[assembly: AssemblyFileVersion("0.2.0.0")] +[assembly: AssemblyVersion("0.4.0.0")] +[assembly: AssemblyFileVersion("0.4.0.0")] diff --git a/MeadeAutostar497/Properties/Resources.Designer.cs b/Meade.net.Telescope/Properties/Resources.Designer.cs similarity index 95% rename from MeadeAutostar497/Properties/Resources.Designer.cs rename to Meade.net.Telescope/Properties/Resources.Designer.cs index 9d71803..67f90ec 100644 --- a/MeadeAutostar497/Properties/Resources.Designer.cs +++ b/Meade.net.Telescope/Properties/Resources.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace ASCOM.MeadeAutostar497.Properties { +namespace ASCOM.Meade.net.Properties { using System; @@ -39,7 +39,7 @@ namespace ASCOM.MeadeAutostar497.Properties { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ASCOM.MeadeAutostar497.Properties.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ASCOM.Meade.net.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; diff --git a/MeadeAutostar497/Properties/Resources.resx b/Meade.net.Telescope/Properties/Resources.resx similarity index 100% rename from MeadeAutostar497/Properties/Resources.resx rename to Meade.net.Telescope/Properties/Resources.resx diff --git a/MeadeAutostar497/Properties/Settings.Designer.cs b/Meade.net.Telescope/Properties/Settings.Designer.cs similarity index 95% rename from MeadeAutostar497/Properties/Settings.Designer.cs rename to Meade.net.Telescope/Properties/Settings.Designer.cs index a2bb372..f1cc444 100644 --- a/MeadeAutostar497/Properties/Settings.Designer.cs +++ b/Meade.net.Telescope/Properties/Settings.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace ASCOM.MeadeAutostar497.Properties { +namespace ASCOM.Meade.net.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] diff --git a/MeadeAutostar497/Properties/Settings.settings b/Meade.net.Telescope/Properties/Settings.settings similarity index 100% rename from MeadeAutostar497/Properties/Settings.settings rename to Meade.net.Telescope/Properties/Settings.settings diff --git a/MeadeAutostar497/AscomClasses/Rates.cs b/Meade.net.Telescope/Rates.cs similarity index 93% rename from MeadeAutostar497/AscomClasses/Rates.cs rename to Meade.net.Telescope/Rates.cs index bd29429..2230bf7 100644 --- a/MeadeAutostar497/AscomClasses/Rates.cs +++ b/Meade.net.Telescope/Rates.cs @@ -7,18 +7,18 @@ using ASCOM.DeviceInterface; using System.Collections; using System.Threading; -namespace ASCOM.MeadeAutostar497 +namespace ASCOM.Meade.net { #region Rate class // // The Rate class implements IRate, and is used to hold values // for AxisRates. You do not need to change this class. // - // The Guid attribute sets the CLSID for ASCOM.MeadeAutostar497.Rate + // The Guid attribute sets the CLSID for ASCOM.Meade.net.Rate // The ClassInterface/None addribute prevents an empty interface called // _Rate from being created and used as the [default] interface // - [Guid("20c14d35-a61b-4c6a-a6ab-cb9f27997c45")] + [Guid("288838d1-bbf9-4ce0-9ee1-86ecf38b45c9")] [ClassInterface(ClassInterfaceType.None)] [ComVisible(true)] public class Rate : ASCOM.DeviceInterface.IRate @@ -65,11 +65,11 @@ namespace ASCOM.MeadeAutostar497 // both COM and .NET. The IAxisRates and IEnumerable interfaces provide // this polymorphism. // - // The Guid attribute sets the CLSID for ASCOM.MeadeAutostar497.AxisRates + // The Guid attribute sets the CLSID for ASCOM.Meade.net.AxisRates // The ClassInterface/None addribute prevents an empty interface called // _AxisRates from being created and used as the [default] interface // - [Guid("ac703603-bcfc-4d98-9de3-c2b9a165756f")] + [Guid("436de2dd-a77a-41ad-8a9e-14c3695f18f2")] [ClassInterface(ClassInterfaceType.None)] [ComVisible(true)] public class AxisRates : IAxisRates, IEnumerable @@ -100,7 +100,7 @@ namespace ASCOM.MeadeAutostar497 // TODO Initialize this array with any Primary axis rates that your driver may provide // Example: m_Rates = new Rate[] { new Rate(10.5, 30.2), new Rate(54.0, 43.6) } //this.rates = new Rate[0]; - this.rates = new Rate[] {new Rate(1, 1), new Rate(2, 2), new Rate(3, 3), new Rate(4, 4)}; + this.rates = new Rate[] { new Rate(1, 1), new Rate(2, 2), new Rate(3, 3), new Rate(4, 4) }; break; case TelescopeAxes.axisSecondary: // TODO Initialize this array with any Secondary axis rates that your driver may provide @@ -146,7 +146,7 @@ namespace ASCOM.MeadeAutostar497 // both COM and .NET. The ITrackingRates and IEnumerable interfaces provide // this polymorphism. // - // The Guid attribute sets the CLSID for ASCOM.MeadeAutostar497.TrackingRates + // The Guid attribute sets the CLSID for ASCOM.Meade.net.TrackingRates // The ClassInterface/None addribute prevents an empty interface called // _TrackingRates from being created and used as the [default] interface // @@ -154,7 +154,7 @@ namespace ASCOM.MeadeAutostar497 // will work with this .NET 4.0 object. Changes to this have proved to be challenging // and it is strongly suggested that it isn't changed. // - [Guid("cb732953-8e5a-4bf0-b3b7-451edb74b5d5")] + [Guid("8e9aa30e-ab24-4a20-8af3-4a057defb1ff")] [ClassInterface(ClassInterfaceType.None)] [ComVisible(true)] public class TrackingRates : ITrackingRates, IEnumerable, IEnumerator @@ -176,7 +176,7 @@ namespace ASCOM.MeadeAutostar497 // the tracking rates supported by your telescope. The one value // (tracking rate) that MUST be supported is driveSidereal! // - this.trackingRates = new[] { DriveRates.driveSidereal }; + this.trackingRates = new[] { DriveRates.driveSidereal, DriveRates.driveLunar }; // TODO Initialize this array with any additional tracking rates that your driver may provide } diff --git a/MeadeAutostar497/ReadMe.htm b/Meade.net.Telescope/ReadMe.htm similarity index 100% rename from MeadeAutostar497/ReadMe.htm rename to Meade.net.Telescope/ReadMe.htm diff --git a/MeadeAutostar497/Resources/ASCOM.bmp b/Meade.net.Telescope/Resources/ASCOM.bmp similarity index 100% rename from MeadeAutostar497/Resources/ASCOM.bmp rename to Meade.net.Telescope/Resources/ASCOM.bmp diff --git a/MeadeAutostar497/StringExtensions.cs b/Meade.net.Telescope/StringExtensions.cs similarity index 51% rename from MeadeAutostar497/StringExtensions.cs rename to Meade.net.Telescope/StringExtensions.cs index e6a2cd1..85341c5 100644 --- a/MeadeAutostar497/StringExtensions.cs +++ b/Meade.net.Telescope/StringExtensions.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ASCOM.MeadeAutostar497 +namespace ASCOM.Meade.net { public static class StringExtensions { @@ -13,4 +7,4 @@ namespace ASCOM.MeadeAutostar497 return int.Parse(str); } } -} +} \ No newline at end of file diff --git a/Meade.net.Telescope/Telescope.cs b/Meade.net.Telescope/Telescope.cs new file mode 100644 index 0000000..f16ff1b --- /dev/null +++ b/Meade.net.Telescope/Telescope.cs @@ -0,0 +1,1672 @@ +//tabs=4 +// -------------------------------------------------------------------------------- +// TODO fill in this information for your driver, then remove this line! +// +// ASCOM Telescope driver for Meade.net +// +// Description: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam +// nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam +// erat, sed diam voluptua. At vero eos et accusam et justo duo +// dolores et ea rebum. Stet clita kasd gubergren, no sea takimata +// sanctus est Lorem ipsum dolor sit amet. +// +// Implements: ASCOM Telescope interface version: +// Author: (XXX) Your N. Here +// +// Edit Log: +// +// Date Who Vers Description +// ----------- --- ----- ------------------------------------------------------- +// dd-mmm-yyyy XXX 6.0.0 Initial edit, created from ASCOM driver template +// -------------------------------------------------------------------------------- +// + + +// This is used to define code in the template that is specific to one class implementation +// unused code canbe deleted and this definition removed. +#define Telescope + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Runtime.InteropServices; + +using ASCOM; +using ASCOM.Astrometry; +using ASCOM.Astrometry.AstroUtils; +using ASCOM.Utilities; +using ASCOM.DeviceInterface; +using System.Globalization; +using System.Collections; +using System.Reflection; + +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 + // + // TODO 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.net Telescope")] + [ClassInterface(ClassInterfaceType.None)] + 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"; + internal static string driverID = Marshal.GenerateProgIdForType(MethodBase.GetCurrentMethod().DeclaringType); + + // TODO Change the descriptive string for your driver then remove this line + /// + /// Driver description that displays in the ASCOM Chooser. + /// + private static string driverDescription = "Meade Generic"; + + internal static string comPortProfileName = "COM Port"; // Constants used for Profile persistence + internal static string comPortDefault = "COM1"; + internal static string traceStateProfileName = "Trace Level"; + internal static string traceStateDefault = "false"; + + internal static string comPort; // Variables to hold the currrent device configuration + + /// + /// Private variable to hold the connected state + /// + private bool connectedState; + + /// + /// Private variable to hold an ASCOM Utilities object + /// + private Util utilities; + + /// + /// Private variable to hold an ASCOM AstroUtilities object to provide the Range method + /// + private AstroUtils astroUtilities; + + /// + /// Variable to hold the trace logger object (creates a diagnostic log file with information that you specify) + /// + internal static TraceLogger tl; + + /// + /// Initializes a new instance of the class. + /// Must be public for COM registration. + /// + public Telescope() + { + tl = new TraceLogger("", "Meade.net"); + ReadProfile(); // Read device configuration from the ASCOM Profile store + + tl.LogMessage("Telescope", "Starting initialisation"); + + connectedState = false; // Initialise connected to false + utilities = new Util(); //Initialise util object + astroUtilities = new AstroUtils(); // Initialise astro utilities object + //TODO: Implement your additional construction here + + tl.LogMessage("Telescope", "Completed initialisation"); + } + + + // + // 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() + { + SharedResources.SetupDialog(); + ReadProfile(); + //// 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 + { + tl.LogMessage("SupportedActions Get", "Returning empty arraylist"); + return new ArrayList(); + } + } + + public string Action(string actionName, string actionParameters) + { + LogMessage("", "Action {0}, parameters {1} not implemented", actionName, actionParameters); + throw new ASCOM.ActionNotImplementedException("Action " + actionName + + " is not implemented by this driver"); + } + + public void CommandBlind(string command, bool raw) + { + CheckConnected("CommandBlind"); + // Call CommandString and return as soon as it finishes + //this.CommandString(command, raw); + SharedResources.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 ASCOM.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 SharedResources.SendString(command); + //throw new ASCOM.MethodNotImplementedException("CommandString"); + } + + public void Dispose() + { + // Clean up the tracelogger and util objects + tl.Enabled = false; + tl.Dispose(); + tl = null; + utilities.Dispose(); + utilities = null; + astroUtilities.Dispose(); + astroUtilities = null; + } + + public bool Connected + { + get + { + LogMessage("Connected", "Get {0}", IsConnected); + return IsConnected; + } + set + { + tl.LogMessage("Connected", "Set {0}", value); + if (value == IsConnected) + return; + + if (value) + { + LogMessage("Connected Set", "Connecting to port {0}", comPort); + SharedResources.Connect("Serial"); + connectedState = true; + } + else + { + LogMessage("Connected Set", "Disconnecting from port {0}", comPort); + SharedResources.Disconnect("Serial"); + connectedState = false; + } + } + } + + public string Description + { + // TODO customise this device description + get + { + tl.LogMessage("Description Get", driverDescription); + return driverDescription; + } + } + + public string DriverInfo + { + get + { + Version version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; + // TODO customise this driver description + string driverInfo = "Information about the driver itself. Version: " + + String.Format(CultureInfo.InvariantCulture, "{0}.{1}", version.Major, + version.Minor); + tl.LogMessage("DriverInfo Get", driverInfo); + return driverInfo; + } + } + + public string DriverVersion + { + get + { + Version version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; + string driverVersion = + String.Format(CultureInfo.InvariantCulture, "{0}.{1}", version.Major, version.Minor); + tl.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 = SharedResources.SendString(":GVP#"); + ////:GVP# Get Telescope Product Name + ////Returns: # + + //var firmwareVersion = SharedResources.SendString(":GVN#"); + ////:GVN# Get Telescope Firmware Number + ////Returns: dd.d# + + //string name = $"{telescopeProduceName} - {firmwareVersion}"; + string name = driverDescription; + tl.LogMessage("Name Get", name); + return name; + } + } + + #endregion + + #region ITelescope Implementation + + public void AbortSlew() + { + tl.LogMessage("AbortSlew", "Aborting slew"); + SharedResources.SendBlind(":Q#"); + } + + public AlignmentModes AlignmentMode + { + get + { + tl.LogMessage("AlignmentMode Get", "Getting alignmode"); + + const char ack = (char) 6; + + var alignmentString = SharedResources.SendChar(ack.ToString()); + + //todo implement GW Command + //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}"); + } + + tl.LogMessage("AlignmentMode Get", $"alignmode = {alignmentMode}"); + return alignmentMode; + } + set + { + switch (value) + { + case AlignmentModes.algAltAz: + SharedResources.SendBlind(":AA#"); + //:AA# Sets telescope the AltAz alignment mode + //Returns: nothing + break; + case AlignmentModes.algPolar: + case AlignmentModes.algGermanPolar: + SharedResources.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 + { + //todo firmware bug in 44Eg, :GA# is returning the dec, not the altitude! + //var result = SharedResources.SendString(":GA#"); + ////:GA# Get Telescope Altitude + ////Returns: sDD* MM# or sDD*MMSS# + ////The current scope altitude. The returned format depending on the current precision setting. + + //var alt = utilities.DMSToDegrees(result); + //tl.LogMessage("Altitude", $"{alt}"); + //return alt; + + tl.LogMessage("Altitude Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("Altitude", false); + } + } + + public double ApertureArea + { + get + { + tl.LogMessage("ApertureArea Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("ApertureArea", false); + } + } + + public double ApertureDiameter + { + get + { + tl.LogMessage("ApertureDiameter Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("ApertureDiameter", false); + } + } + + public bool AtHome + { + get + { + tl.LogMessage("AtHome", "Get - " + false.ToString()); + return false; + } + } + + private bool _atPark = false; + + public bool AtPark + { + get + { + tl.LogMessage("AtPark", "Get - " + _atPark); + return _atPark; + } + private set { _atPark = value; } + } + + public IAxisRates AxisRates(TelescopeAxes Axis) + { + tl.LogMessage("AxisRates", "Get - " + Axis.ToString()); + return new AxisRates(Axis); + } + + public double Azimuth + { + get + { + var result = SharedResources.SendString(":GZ#"); + //:GZ# Get telescope azimuth + //Returns: DDD*MM#T or DDD*MMSS# + //The current telescope Azimuth depending on the selected precision. + + double az = utilities.DMSToDegrees(result); + + tl.LogMessage("Azimuth Get", $"{az}"); + return az; + } + } + + public bool CanFindHome + { + get + { + tl.LogMessage("CanFindHome", "Get - " + false.ToString()); + return false; + } + } + + public bool CanMoveAxis(TelescopeAxes Axis) + { + tl.LogMessage("CanMoveAxis", "Get - " + Axis.ToString()); + 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 + { + tl.LogMessage("CanPark", "Get - " + true.ToString()); + return true; + } + } + + public bool CanPulseGuide + { + get + { + tl.LogMessage("CanPulseGuide", "Get - " + true.ToString()); + return true; + } + } + + public bool CanSetDeclinationRate + { + get + { + tl.LogMessage("CanSetDeclinationRate", "Get - " + false.ToString()); + return false; + } + } + + public bool CanSetGuideRates + { + get + { + tl.LogMessage("CanSetGuideRates", "Get - " + false.ToString()); + return false; + } + } + + public bool CanSetPark + { + get + { + tl.LogMessage("CanSetPark", "Get - " + false.ToString()); + return false; + } + } + + public bool CanSetPierSide + { + get + { + tl.LogMessage("CanSetPierSide", "Get - " + false.ToString()); + return false; + } + } + + public bool CanSetRightAscensionRate + { + get + { + tl.LogMessage("CanSetRightAscensionRate", "Get - " + false.ToString()); + return false; + } + } + + public bool CanSetTracking + { + get + { + tl.LogMessage("CanSetTracking", "Get - " + true.ToString()); + return true; + } + } + + public bool CanSlew + { + get + { + tl.LogMessage("CanSlew", "Get - " + true.ToString()); + return true; + } + } + + public bool CanSlewAltAz + { + get + { + tl.LogMessage("CanSlewAltAz", "Get - " + true.ToString()); + return true; + } + } + + public bool CanSlewAltAzAsync + { + get + { + tl.LogMessage("CanSlewAltAzAsync", "Get - " + true.ToString()); + return true; + } + } + + public bool CanSlewAsync + { + get + { + tl.LogMessage("CanSlewAsync", "Get - " + true.ToString()); + return true; + } + } + + public bool CanSync + { + get + { + tl.LogMessage("CanSync", "Get - " + true.ToString()); + return true; + } + } + + public bool CanSyncAltAz + { + get + { + tl.LogMessage("CanSyncAltAz", "Get - " + false.ToString()); + return false; + } + } + + public bool CanUnpark + { + get + { + tl.LogMessage("CanUnpark", "Get - " + false.ToString()); + return false; + } + } + + public double Declination + { + get + { + var result = SharedResources.SendString(":GD#"); + //:GD# Get Telescope Declination. + //Returns: sDD* MM# or sDD*MMSS# + //Depending upon the current precision setting for the telescope. + + double declination = utilities.DMSToDegrees(result); + + tl.LogMessage("Declination", "Get - " + utilities.DegreesToDMS(declination, ":", ":")); + return declination; + } + } + + public double DeclinationRate + { + get + { + double declination = 0.0; + tl.LogMessage("DeclinationRate", "Get - " + declination.ToString()); + return declination; + } + set + { + tl.LogMessage("DeclinationRate Set", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("DeclinationRate", true); + } + } + + public PierSide DestinationSideOfPier(double rightAscension, double declination) + { + tl.LogMessage("DestinationSideOfPier Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("DestinationSideOfPier", false); + } + + public bool DoesRefraction + { + get + { + tl.LogMessage("DoesRefraction Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("DoesRefraction", false); + } + set + { + tl.LogMessage("DoesRefraction Set", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("DoesRefraction", true); + } + } + + public EquatorialCoordinateType EquatorialSystem + { + get + { + EquatorialCoordinateType equatorialSystem = EquatorialCoordinateType.equTopocentric; + tl.LogMessage("DeclinationRate", "Get - " + equatorialSystem.ToString()); + return equatorialSystem; + } + } + + public void FindHome() + { + tl.LogMessage("FindHome", "Not implemented"); + throw new ASCOM.MethodNotImplementedException("FindHome"); + } + + public double FocalLength + { + get + { + tl.LogMessage("FocalLength Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("FocalLength", false); + } + } + + public double GuideRateDeclination + { + get + { + tl.LogMessage("GuideRateDeclination Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("GuideRateDeclination", false); + } + set + { + tl.LogMessage("GuideRateDeclination Set", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("GuideRateDeclination", true); + } + } + + public double GuideRateRightAscension + { + get + { + tl.LogMessage("GuideRateRightAscension Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("GuideRateRightAscension", false); + } + set + { + tl.LogMessage("GuideRateRightAscension Set", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("GuideRateRightAscension", true); + } + } + + public bool IsPulseGuiding + { + get + { + tl.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) + { + tl.LogMessage("MoveAxis", $"Axis={axis} rate={rate}"); + + 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: + SharedResources.SendBlind(":RG#"); + //:RG# Set Slew rate to Guiding Rate (slowest) + //Returns: Nothing + break; + case 2: + SharedResources.SendBlind(":RC#"); + //:RC# Set Slew rate to Centering rate (2nd slowest) + //Returns: Nothing + break; + case 3: + SharedResources.SendBlind(":RM#"); + //:RM# Set Slew rate to Find Rate (2nd Fastest) + //Returns: Nothing + break; + case 4: + SharedResources.SendBlind(":RS#"); + //:RS# Set Slew rate to max (fastest) + //Returns: Nothing + break; + default: + throw new ASCOM.InvalidValueException($"Rate {rate} not supported"); + } + + switch (axis) + { + case TelescopeAxes.axisPrimary: + if (rate == 0) + { + _movingPrimary = false; + SharedResources.SendBlind(":Qe#"); + //:Qe# Halt eastward Slews + //Returns: Nothing + SharedResources.SendBlind(":Qw#"); + //:Qw# Halt westward Slews + //Returns: Nothing + } + else if (rate > 0) + { + SharedResources.SendBlind(":Me#"); + //:Me# Move Telescope East at current slew rate + //Returns: Nothing + _movingPrimary = true; + } + else + { + SharedResources.SendBlind(":Mw#"); + //:Mw# Move Telescope West at current slew rate + //Returns: Nothing + _movingPrimary = true; + } + + break; + case TelescopeAxes.axisSecondary: + if (rate == 0) + { + _movingSecondary = false; + SharedResources.SendBlind(":Qn#"); + //:Qn# Halt northward Slews + //Returns: Nothing + SharedResources.SendBlind(":Qs#"); + //:Qs# Halt southward Slews + //Returns: Nothing + } + else if (rate > 0) + { + SharedResources.SendBlind(":Mn#"); + //:Mn# Move Telescope North at current slew rate + //Returns: Nothing + _movingSecondary = true; + } + else + { + SharedResources.SendBlind(":Ms#"); + //:Ms# Move Telescope South at current slew rate + //Returns: Nothing + _movingSecondary = true; + } + + break; + default: + throw new ASCOM.MethodNotImplementedException("Can not move this axis."); + } + } + + public void Park() + { + tl.LogMessage("Park", "Parking telescope"); + + if (AtPark) + return; + + SharedResources.SendBlind(":hP#"); + AtPark = true; + } + + private readonly bool + _userNewerPulseGuiding = true; //todo make this a device setting based on firmware revision + + public void PulseGuide(GuideDirections direction, int duration) + { + tl.LogMessage("PulseGuide", $"pulse guide direction {direction} duration {duration}"); + 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; + } + + if (_userNewerPulseGuiding) + { + SharedResources.SendBlind($":Mg{d}{duration:0000}#"); + utilities.WaitForMilliseconds(duration); //todo figure out if this is really needed + } + else + { + SharedResources.Lock(() => + { + SharedResources.SendBlind(":RG#"); //Make sure we are at guide rate + SharedResources.SendBlind($":M{d}#"); + utilities.WaitForMilliseconds(duration); + SharedResources.SendBlind($":Q{d}#"); + }); + } + } + + public double RightAscension + { + get + { + var result = SharedResources.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); + + tl.LogMessage("RightAscension", "Get - " + utilities.HoursToHMS(rightAscension)); + return rightAscension; + } + } + + public double RightAscensionRate + { + get + { + double rightAscensionRate = 0.0; + tl.LogMessage("RightAscensionRate", "Get - " + rightAscensionRate.ToString()); + return rightAscensionRate; + } + set + { + tl.LogMessage("RightAscensionRate Set", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("RightAscensionRate", true); + } + } + + public void SetPark() + { + tl.LogMessage("SetPark", "Not implemented"); + throw new ASCOM.MethodNotImplementedException("SetPark"); + } + + public PierSide SideOfPier + { + get + { + tl.LogMessage("SideOfPier Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("SideOfPier", false); + } + set + { + tl.LogMessage("SideOfPier Set", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("SideOfPier", true); + } + } + + public double SiderealTime + { + get + { + // Now using NOVAS 3.1 + double siderealTime = 0.0; + using (var novas = new ASCOM.Astrometry.NOVAS.NOVAS31()) + { + var jd = utilities.DateUTCToJulian(DateTime.UtcNow); + novas.SiderealTime(jd, 0, novas.DeltaT(jd), + ASCOM.Astrometry.GstType.GreenwichApparentSiderealTime, + ASCOM.Astrometry.Method.EquinoxBased, + ASCOM.Astrometry.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); + + tl.LogMessage("SiderealTime", "Get - " + siderealTime.ToString()); + return siderealTime; + } + } + + public double SiteElevation + { + get + { + tl.LogMessage("SiteElevation Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("SiteElevation", false); + } + set + { + tl.LogMessage("SiteElevation Set", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("SiteElevation", true); + } + } + + public double SiteLatitude + { + get + { + var latitude = SharedResources.SendString(":Gt#"); + + var siteLatitude = utilities.DMSToDegrees(latitude); + tl.LogMessage("SiteLatitude Get", $"{utilities.DegreesToDMS(siteLatitude)}"); + return siteLatitude; + } + set + { + tl.LogMessage("SiteLatitude Set", $"{utilities.DegreesToDMS(value)}"); + + 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."); + + int d = Convert.ToInt32(Math.Floor(value)); + int m = Convert.ToInt32(60 * (value - d)); + + var result = SharedResources.SendChar($":Sts{d:00}*{m:00}#"); + if (result != "1") + throw new InvalidOperationException("Failed to set site latitude."); + } + } + + public double SiteLongitude + { + get + { + var longitude = SharedResources.SendString(":Gg#"); + double siteLongitude = utilities.DMSToDegrees(longitude); + + if (siteLongitude > 180) + siteLongitude = siteLongitude - 360; + + tl.LogMessage("SiteLongitude Get", $"{utilities.DegreesToDMS(siteLongitude)}"); + return siteLongitude; + } + set + { + tl.LogMessage("SiteLongitude Set", $"{utilities.DegreesToDMS(value)}"); + if (value > 180) + throw new InvalidValueException("Longitude cannot be greater than 180 degrees."); + + if (value < -180) + throw new InvalidValueException("Longitude cannot be lower than -180 degrees."); + + int d = Convert.ToInt32(Math.Floor(value)); + int m = Convert.ToInt32(60 * (value - d)); + + var result = SharedResources.SendChar($":Sg{d:000}*{m:00}#"); + if (result != "1") + throw new InvalidOperationException("Failed to set site longitude."); + } + } + + public short SlewSettleTime + { + get + { + tl.LogMessage("SlewSettleTime Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("SlewSettleTime", false); + } + set + { + tl.LogMessage("SlewSettleTime Set", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("SlewSettleTime", true); + } + } + + public void SlewToAltAz(double azimuth, double altitude) + { + tl.LogMessage("SlewToAltAz", $"Az=~{azimuth} Alt={altitude}"); + + SlewToAltAzAsync(azimuth, altitude); + + while (Slewing) //wait for slew to complete + { + utilities.WaitForMilliseconds(200); //be responsive to AbortSlew(); + } + } + + private double TargetAltitude + { + set + { + if (value > 90) + throw new ASCOM.InvalidValueException("Altitude cannot be greater than 90."); + + if (value < 0) + throw new ASCOM.InvalidValueException("Altitide cannot be less than 0."); + + + var dms = utilities.DegreesToDMS(value, "*", "'", ".", 2); + var s = value < 0 ? '-' : '+'; + + var result = SharedResources.SendChar($":Sa{s}{dms}#"); + //:SasDD*MM# + //Set target object altitude to sDD*MM# or sDD*MMSS# [LX 16, Autostar, Autostar II] + //Returns: + //1 Object within slew range + //0 Object out of slew range + + if (result == "0") + throw new ASCOM.InvalidOperationException("Target altitude out of slew range"); + } + } + + private double TargetAzimuth + { + set + { + if (value >= 360) + throw new ASCOM.InvalidValueException("Azimuth cannot be 360 or higher."); + + if (value < 0) + throw new ASCOM.InvalidValueException("Azimuth cannot be less than 0."); + + var dms = utilities.DegreesToDM(value, "*", ":", 2); + + var result = SharedResources.SendChar($":Sd{dms}#"); + //:SzDDD*MM# + //Sets the target Object Azimuth[LX 16 and Autostar II only] + //Returns: + //0 Invalid + //1 - Valid + + if (result == "0") + throw new ASCOM.InvalidOperationException("Target Azimuth out of slew range"); + + } + } + + public void SlewToAltAzAsync(double azimuth, double altitude) + { + tl.LogMessage("SlewToAltAzAsync", $"Az=~{azimuth} Alt={altitude}"); + + SharedResources.Lock(() => + { + TargetAltitude = altitude; + TargetAzimuth = azimuth; + + DoSlewAsync(false); + }); + } + + private void DoSlewAsync(bool polar) + { + SharedResources.Lock(() => + { + switch (polar) + { + case true: + var response = SharedResources.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. + break; + case "1": + //Below Horizon + string belowHorizonMessage = SharedResources.ReadTerminated(); + throw new ASCOM.InvalidOperationException(belowHorizonMessage); + case "2": + //Below Horizon + string belowMinimumElevationMessage = SharedResources.ReadTerminated(); + throw new ASCOM.InvalidOperationException(belowMinimumElevationMessage); + default: + throw new ASCOM.DriverException("This error should not happen"); + + } + + break; + case false: + var maResponse = SharedResources.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 ASCOM.InvalidOperationException("fault"); + } + + break; + } + }); + } + + public void SlewToCoordinates(double rightAscension, double declination) + { + tl.LogMessage("SlewToCoordinates", $"Ra={rightAscension}, Dec={declination}"); + SlewToCoordinatesAsync(rightAscension, declination); + + while (Slewing) //wait for slew to complete + { + utilities.WaitForMilliseconds(200); //be responsive to AbortSlew(); + } + } + + public void SlewToCoordinatesAsync(double rightAscension, double declination) + { + tl.LogMessage("SlewToCoordinatesAsync", $"Ra={rightAscension}, Dec={declination}"); + + SharedResources.Lock(() => + { + TargetRightAscension = rightAscension; + TargetDeclination = declination; + + DoSlewAsync(true); + } + ); + } + + public void SlewToTarget() + { + tl.LogMessage("SlewToTarget", "Executing"); + SlewToTargetAsync(); + + while (Slewing) + { + utilities.WaitForMilliseconds(200); + } + } + + private const double INVALID_PARAMETER = -1000; + + public void SlewToTargetAsync() + { + if (TargetDeclination == INVALID_PARAMETER || TargetRightAscension == INVALID_PARAMETER) + throw new ASCOM.InvalidOperationException("No target selected to slew to."); + + DoSlewAsync(true); + } + + private bool movingAxis() + { + return _movingPrimary || _movingSecondary; + } + + public bool Slewing + { + get + { + if (!Connected) return false; + + + if (movingAxis()) + return true; + + var result = SharedResources.SendString(":D#"); + bool isSlewing = result != string.Empty; + + tl.LogMessage("Slewing Get", $"Result = {isSlewing}"); + return isSlewing; + } + } + + public void SyncToAltAz(double azimuth, double altitude) + { + tl.LogMessage("SyncToAltAz", "Not implemented"); + throw new ASCOM.MethodNotImplementedException("SyncToAltAz"); + } + + public void SyncToCoordinates(double rightAscension, double declination) + { + tl.LogMessage("SyncToCoordinates", $"RA={rightAscension} Dec={declination}"); + + SharedResources.Lock(() => + { + TargetRightAscension = rightAscension; + TargetDeclination = declination; + + SyncToTarget(); + }); + } + + public void SyncToTarget() + { + tl.LogMessage("SyncToTarget", "Executing"); + var result = SharedResources.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 - At static string: " M31 EX GAL MAG 3.5 SZ178.0'#" + + if (result == string.Empty) + throw new ASCOM.InvalidOperationException("Unable to perform sync"); + } + + private double _targetDeclination = INVALID_PARAMETER; + public double TargetDeclination + { + get + { + if (_targetDeclination == INVALID_PARAMETER) + throw new ASCOM.InvalidOperationException("Target not set"); + + //var result = SerialPort.CommandTerminated(":Gd#", "#"); + ////:Gd# Get Currently Selected Object/Target Declination + ////Returns: sDD* MM# or sDD*MMSS# + ////Depending upon the current precision setting for the telescope. + + //double targetDec = DmsToDouble(result); + + //return targetDec; + + tl.LogMessage("TargetDeclination Get", $"{_targetDeclination}"); + return _targetDeclination; + } + set + { + tl.LogMessage("TargetDeclination Set", $"{value}"); + + //todo implement low precision version of this. + if (value > 90) + throw new ASCOM.InvalidValueException("Declination cannot be greater than 90."); + + if (value < -90) + throw new ASCOM.InvalidValueException("Declination cannot be less than -90."); + + + var dms = utilities.DegreesToDMS(value, "*", ":", ":", 2); + var s = value < 0 ? '-' : '+'; + + var result = SharedResources.SendChar($":Sd{s}{dms}#"); + //: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 ASCOM.InvalidOperationException("Target declination invalid"); + } + + _targetDeclination = value; + } + } + + private double _targetRightAscension = INVALID_PARAMETER; + public double TargetRightAscension + { + get + { + if (_targetRightAscension == INVALID_PARAMETER) + throw new ASCOM.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; + + tl.LogMessage("TargetRightAscension Get", $"{_targetRightAscension}"); + return _targetRightAscension; + } + set + { + tl.LogMessage("TargetRightAscension Set", $"{value}"); + + 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 = SharedResources.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 + { + tl.LogMessage("Tracking", $"Get - {_tracking}"); + return _tracking; + } + set + { + tl.LogMessage($"Tracking Set", $"{value}"); + _tracking = value; + } + } + + private DriveRates _trackingRate = DriveRates.driveSidereal; + public DriveRates TrackingRate + { + get + { + //todo implement this with the GW command + //var result = SerialPort.CommandTerminated(":GT#", "#"); + + //double rate = double.Parse(result); + + + //if (rate == 60.1) + // return DriveRates.driveLunar; + //else if (rate == 60.1) + // return DriveRates.driveSidereal; + + //return DriveRates.driveKing; + tl.LogMessage("TrackingRate Get", $"{_trackingRate}"); + return _trackingRate; + } + set + { + tl.LogMessage("TrackingRate Set", $"{value}"); + + switch (value) + { + case DriveRates.driveSidereal: + SharedResources.SendBlind(":TQ#"); + //:TQ# Selects sidereal tracking rate + //Returns: Nothing + break; + case DriveRates.driveLunar: + SharedResources.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(); + tl.LogMessage("TrackingRates", "Get - "); + foreach (DriveRates driveRate in trackingRates) + { + tl.LogMessage("TrackingRates", "Get - " + driveRate.ToString()); + } + return trackingRates; + } + } + + private TimeSpan GetUtcCorrection() + { + string utcOffSet = SharedResources.SendString(":GG#"); + double utcOffsetHours = double.Parse(utcOffSet); + TimeSpan utcCorrection = TimeSpan.FromHours(utcOffsetHours); + return utcCorrection; + } + + private class TelescopeDateDetails + { + public string telescopeDate { get; set; } + public string telescopeTime { get; set; } + public TimeSpan utcCorrection { get; set; } + } + + public DateTime UTCDate + { + get + { + tl.LogMessage("UTCDate", "Get started"); + + TelescopeDateDetails telescopeDateDetails = SharedResources.Lock(() => + { + TelescopeDateDetails tdd = new TelescopeDateDetails(); + tdd.telescopeDate = SharedResources.SendString(":GC#"); + tdd.telescopeTime = SharedResources.SendString(":GL#"); + tdd.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(); + + //Todo is this telescope local time, or real utc? + var utcDate = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc) + + telescopeDateDetails.utcCorrection; + + tl.LogMessage("UTCDate", "Get - " + utcDate.ToString("MM/dd/yy HH:mm:ss")); + + return utcDate; + } + set + { + tl.LogMessage("UTCDate", "Set - " + value.ToString("MM/dd/yy HH:mm:ss")); + + SharedResources.Lock(() => + { + var utcCorrection = GetUtcCorrection(); + var localDateTime = value - utcCorrection; + + //Todo is this telescope local time, or real utc? + var timeResult = SharedResources.SendChar($":SL{localDateTime:HH:mm:ss}#"); + if (timeResult != "1") + { + throw new InvalidOperationException("Failed to set local time"); + } + + var dateResult = SharedResources.SendChar($":SC{localDateTime:MM/dd/yy}#"); + if (dateResult != "1") + { + throw new InvalidOperationException("Failed to set local date"); + } + + //throwing away these two strings which represent + SharedResources.ReadTerminated(); //Updating Planetary Data# + SharedResources.ReadTerminated(); // # + }); + } + } + + public void Unpark() + { + tl.LogMessage("Unpark", "Not implemented"); + throw new ASCOM.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 + + // 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 (var P = new ASCOM.Utilities.Profile()) + { + 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 + { + // TODO check that the driver hardware connection exists and is connected to the hardware + return connectedState; + } + } + + /// + /// Use this function to throw an exception if we aren't connected to the hardware + /// + /// + private void CheckConnected(string message) + { + if (!IsConnected) + { + throw new ASCOM.NotConnectedException(message); + } + } + + /// + /// Read the device configuration from the ASCOM Profile store + /// + internal void ReadProfile() + { + var profileProperties = SharedResources.ReadProfile(); + tl.Enabled = profileProperties.TraceLogger; + comPort = profileProperties.ComPort; + } + + /// + /// Log helper function that takes formatted strings and arguments + /// + /// + /// + /// + internal static void LogMessage(string identifier, string message, params object[] args) + { + var msg = string.Format(message, args); + tl.LogMessage(identifier, msg); + } + #endregion + } +} diff --git a/MeadeAutostar497/app.config b/Meade.net.Telescope/app.config similarity index 93% rename from MeadeAutostar497/app.config rename to Meade.net.Telescope/app.config index dd30df1..56895a9 100644 --- a/MeadeAutostar497/app.config +++ b/Meade.net.Telescope/app.config @@ -5,4 +5,4 @@
- + diff --git a/Meade.net.focuser/ASCOM.ico b/Meade.net.focuser/ASCOM.ico new file mode 100644 index 0000000000000000000000000000000000000000..9bf8f4110f20bfdde720ab5e04def9b01b414268 GIT binary patch literal 125783 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 literal 0 HcmV?d00001 diff --git a/Meade.net.focuser/ASCOMDriverTemplate.snk b/Meade.net.focuser/ASCOMDriverTemplate.snk new file mode 100644 index 0000000000000000000000000000000000000000..5ab294522d027053efbc99370de86ab44f7d999f GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096o@;lSCg(u0Cs*BuyKR9r^P7mA>i6=N| z*}>a~-mZ)Ls%FAV}Sq^{wX`Ye#nGu3i15cdI?<@%KFXx_Qu1Q zN>O%rp3WAnZ)@;RL`$uw-03doE|FK(K%8USc|TsTgk^<-c1|7cI%~?31LIyTy58J< z(taLe4Y_8Nh}Or>g|R?a+P~+Jr3m-}1VHV}8MG@t3cStINbjmS^i<3>k4h+hz~GNt z!8WZr{qM3aNd*4ISiND;`zskEKm-${7citI0lHIW8_tw=H7N}rUVoSh@Z0OAZLl6E zg4N4tVSk`gTDSF;w7bcn&{MOEFqqy=Ox~K)dzZP}jp0K6s4Gh^sqMDcqM}~JxMG*P z5rI=Fs#&gf4uxH-ATi-cxXakgqSr>inHh9jdLju(8XosCtg!90t5C7nYMaYhglwJV z1mep!noQ3{J9amy(`{qn2B&nJ#uy{GMKAhs)#Zvf}wC>k)weHP-6 z7D;L&Jk#(ossUu=V0_*yfJFz4$gnsHpzT~Y)$N?L)gSilWU%u5(kvc4y5ee=$CX^9 zq+$TozA{2;O}JYE11w~)ND9yKepmrn_1VTcdv35sO)b%cS3!i<;&LB(eJ&j~^ktgh i2?jBBQqyjQmCuueJbfE~HZ)z<*Si*L2C5Qx(CwX1izKQ5 literal 0 HcmV?d00001 diff --git a/Meade.net.focuser/Focuser.cs b/Meade.net.focuser/Focuser.cs new file mode 100644 index 0000000..aa50a5a --- /dev/null +++ b/Meade.net.focuser/Focuser.cs @@ -0,0 +1,570 @@ +//tabs=4 +// -------------------------------------------------------------------------------- +// TODO fill in this information for your driver, then remove this line! +// +// ASCOM Focuser driver for Meade.net +// +// Description: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam +// nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam +// erat, sed diam voluptua. At vero eos et accusam et justo duo +// dolores et ea rebum. Stet clita kasd gubergren, no sea takimata +// sanctus est Lorem ipsum dolor sit amet. +// +// Implements: ASCOM Focuser interface version: +// Author: (XXX) Your N. Here +// +// Edit Log: +// +// Date Who Vers Description +// ----------- --- ----- ------------------------------------------------------- +// dd-mmm-yyyy XXX 6.0.0 Initial edit, created from ASCOM driver template +// -------------------------------------------------------------------------------- +// + + +// This is used to define code in the template that is specific to one class implementation +// unused code canbe deleted and this definition removed. +#define Focuser + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Runtime.InteropServices; + +using ASCOM; +using ASCOM.Astrometry; +using ASCOM.Astrometry.AstroUtils; +using ASCOM.Utilities; +using ASCOM.DeviceInterface; +using System.Globalization; +using System.Collections; +using System.Reflection; +using ASCOM.Utilities.Interfaces; + +namespace ASCOM.Meade.net +{ + // + // Your driver's DeviceID is ASCOM.Meade.net.Focuser + // + // The Guid attribute sets the CLSID for ASCOM.Meade.net.Focuser + // The ClassInterface/None addribute prevents an empty interface called + // _Meade.net from being created and used as the [default] interface + // + // TODO Replace the not implemented exceptions with code to implement the function or + // throw the appropriate ASCOM exception. + // + + /// + /// ASCOM Focuser Driver for Meade.net. + /// + [Guid("a32ac647-bf0f-42f9-8ab0-d166fa5884ad")] + [ProgId("ASCOM.MeadeGeneric.focuser")] + [ServedClassName("Meade.net Focuser")] + [ClassInterface(ClassInterfaceType.None)] + public class Focuser : ReferenceCountedObjectBase, IFocuserV3 + { + /// + /// 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.Focuser"; + internal static string driverID = Marshal.GenerateProgIdForType(MethodBase.GetCurrentMethod().DeclaringType); + // TODO Change the descriptive string for your driver then remove this line + /// + /// Driver description that displays in the ASCOM Chooser. + /// + private static string driverDescription = "Meade Generic"; + + internal static string comPortProfileName = "COM Port"; // Constants used for Profile persistence + internal static string comPortDefault = "COM1"; + internal static string traceStateProfileName = "Trace Level"; + internal static string traceStateDefault = "false"; + + internal static string comPort; // Variables to hold the currrent device configuration + + /// + /// Private variable to hold the connected state + /// + private bool connectedState; + + /// + /// Private variable to hold an ASCOM Utilities object + /// + private Util utilities; + + /// + /// Private variable to hold an ASCOM AstroUtilities object to provide the Range method + /// + private AstroUtils astroUtilities; + + /// + /// Variable to hold the trace logger object (creates a diagnostic log file with information that you specify) + /// + internal static TraceLogger tl; + + /// + /// Initializes a new instance of the class. + /// Must be public for COM registration. + /// + public Focuser() + { + tl = new TraceLogger("", "Meade.net"); + ReadProfile(); // Read device configuration from the ASCOM Profile store + + tl.LogMessage("Focuser", "Starting initialisation"); + + connectedState = false; // Initialise connected to false + utilities = new Util(); //Initialise util object + astroUtilities = new AstroUtils(); // Initialise astro utilities object + + tl.LogMessage("Focuser", "Completed initialisation"); + } + + + // + // PUBLIC COM INTERFACE IFocuserV3 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() + { + SharedResources.SetupDialog(); + ReadProfile(); + } + + public ArrayList SupportedActions + { + get + { + tl.LogMessage("SupportedActions Get", "Returning empty arraylist"); + return new ArrayList(); + } + } + + public string Action(string actionName, string actionParameters) + { + LogMessage("", "Action {0}, parameters {1} not implemented", actionName, actionParameters); + throw new ASCOM.ActionNotImplementedException("Action " + actionName + " is not implemented by this driver"); + } + + public void CommandBlind(string command, bool raw) + { + CheckConnected("CommandBlind"); + // Call CommandString and return as soon as it finishes + //this.CommandString(command, raw); + SharedResources.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 ASCOM.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 SharedResources.SendString(command); + + throw new ASCOM.MethodNotImplementedException("CommandString"); + } + + public void Dispose() + { + // Clean up the tracelogger and util objects + tl.Enabled = false; + tl.Dispose(); + tl = null; + utilities.Dispose(); + utilities = null; + astroUtilities.Dispose(); + astroUtilities = null; + } + + public bool Connected + { + get + { + LogMessage("Connected", "Get {0}", IsConnected); + return IsConnected; + } + set + { + tl.LogMessage("Connected", "Set {0}", value); + if (value == IsConnected) + return; + + if (value) + { + LogMessage("Connected Set", "Connecting to port {0}", comPort); + SharedResources.Connect("Serial"); + connectedState = true; + } + else + { + LogMessage("Connected Set", "Disconnecting from port {0}", comPort); + SharedResources.Disconnect("Serial"); + connectedState = false; + } + } + } + + public string Description + { + // TODO customise this device description + get + { + tl.LogMessage("Description Get", driverDescription); + return driverDescription; + } + } + + public string DriverInfo + { + get + { + Version version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; + // TODO customise this driver description + string driverInfo = "Information about the driver itself. Version: " + String.Format(CultureInfo.InvariantCulture, "{0}.{1}", version.Major, version.Minor); + tl.LogMessage("DriverInfo Get", driverInfo); + return driverInfo; + } + } + + public string DriverVersion + { + get + { + Version version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; + string driverVersion = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", version.Major, version.Minor); + tl.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"; + string name = driverDescription; + tl.LogMessage("Name Get", name); + return name; + } + } + + #endregion + + #region IFocuser Implementation + + public bool Absolute + { + get + { + tl.LogMessage("Absolute Get", false.ToString()); + return false; // This is a relative focuser + } + } + + public void Halt() + { + tl.LogMessage("Halt", "Halting"); + SharedResources.SendBlind(":FQ#"); + //:FQ# Halt Focuser Motion + //Returns: Nothing + } + + public bool IsMoving + { + get + { + tl.LogMessage("IsMoving Get", false.ToString()); + return false; // This focuser always moves instantaneously so no need for IsMoving ever to be True + } + } + + public bool Link + { + get + { + tl.LogMessage("Link Get", this.Connected.ToString()); + return this.Connected; // Direct function to the connected method, the Link method is just here for backwards compatibility + } + set + { + tl.LogMessage("Link Set", value.ToString()); + this.Connected = value; // Direct function to the connected method, the Link method is just here for backwards compatibility + } + } + + private readonly int _maxIncrement = 7000; + public int MaxIncrement + { + get + { + tl.LogMessage("MaxIncrement Get", _maxIncrement.ToString()); + return _maxIncrement; // Maximum change in one move + } + } + + private readonly int _maxStep = 7000; + public int MaxStep + { + get + { + tl.LogMessage("MaxStep Get", _maxStep.ToString()); + return _maxStep; + } + } + + public void Move(int Position) + { + tl.LogMessage("Move", Position.ToString()); + + //todo implement backlash compensation + //todo implement direction reverse + //todo implement dynamic braking + + if (Position < -MaxIncrement || Position > MaxIncrement) + { + throw new ASCOM.InvalidValueException($"position out of range {-MaxIncrement} < {Position} < {MaxIncrement}"); + } + + if (Position == 0) + return; + + if (Position > 0) + { + //desired move direction is out + MoveFocuser(true, Math.Abs(Position)); + } + else + { + //desired move direction is in + MoveFocuser(false, Math.Abs(Position)); + } + } + + private void MoveFocuser(bool directionOut, int steps) + { + SharedResources.Lock(() => + { + SharedResources.SendBlind(directionOut ? ":F+#" : ":F-#"); + //:F+# Start Focuser moving inward (toward objective) + //Returns: None + + //:F-# Start Focuser moving outward (away from objective) + //Returns: None + + utilities.WaitForMilliseconds(steps); + + Halt(); + }); + } + + public int Position + { + get + { + throw new ASCOM.PropertyNotImplementedException("Position", false); + //return focuserPosition; // Return the focuser position + } + } + + public double StepSize + { + get + { + tl.LogMessage("StepSize Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("StepSize", false); + } + } + + public bool TempComp + { + get + { + tl.LogMessage("TempComp Get", false.ToString()); + return false; + } + set + { + tl.LogMessage("TempComp Set", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("TempComp", false); + } + } + + public bool TempCompAvailable + { + get + { + tl.LogMessage("TempCompAvailable Get", false.ToString()); + return false; // Temperature compensation is not available in this driver + } + } + + public double Temperature + { + get + { + tl.LogMessage("Temperature Get", "Not implemented"); + throw new ASCOM.PropertyNotImplementedException("Temperature", false); + } + } + + #endregion + + #region Private properties and methods + // here are some useful properties and methods that can be used as required + // to help with driver development + + #region ASCOM Registration + + // Register or unregister driver for ASCOM. This is harmless if already + // registered or unregistered. + // + /// + /// 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 (var P = new ASCOM.Utilities.Profile()) + { + P.DeviceType = "Focuser"; + 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 + { + // TODO check that the driver hardware connection exists and is connected to the hardware + return connectedState; + } + } + + /// + /// Use this function to throw an exception if we aren't connected to the hardware + /// + /// + private void CheckConnected(string message) + { + if (!IsConnected) + { + throw new ASCOM.NotConnectedException(message); + } + } + + /// + /// Read the device configuration from the ASCOM Profile store + /// + internal void ReadProfile() + { + var profileProperties = SharedResources.ReadProfile(); + tl.Enabled = profileProperties.TraceLogger; + comPort = profileProperties.ComPort; + } + + /// + /// Log helper function that takes formatted strings and arguments + /// + /// + /// + /// + internal static void LogMessage(string identifier, string message, params object[] args) + { + var msg = string.Format(message, args); + tl.LogMessage(identifier, msg); + } + #endregion + } +} diff --git a/Meade.net.focuser/Meade.net.focuser.csproj b/Meade.net.focuser/Meade.net.focuser.csproj new file mode 100644 index 0000000..a082801 --- /dev/null +++ b/Meade.net.focuser/Meade.net.focuser.csproj @@ -0,0 +1,151 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {A97E3AEC-F11D-49DA-B259-DE99DA813A86} + Library + Properties + ASCOM.Meade.net + ASCOM.Meade.net.Focuser + + + + + 3.5 + v4.7.1 + ASCOM.ico + true + ASCOMDriverTemplate.snk + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + + true + full + false + ..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + x86 + false + + + pdbonly + true + ..\bin\Release\ + TRACE + prompt + 4 + AnyCPU + false + false + + + + + + + + + + + + + + + 3.5 + + + + + + + + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + Designer + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + {3689a2cb-94c5-4012-a5cf-7e7d1dd27143} + Meade.net + + + + + + + + + \ No newline at end of file diff --git a/Meade.net.focuser/Properties/AssemblyInfo.cs b/Meade.net.focuser/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0fce7a9 --- /dev/null +++ b/Meade.net.focuser/Properties/AssemblyInfo.cs @@ -0,0 +1,39 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +// TODO - Add your authorship information here +[assembly: AssemblyTitle("ASCOM.Meade.net.Focuser")] +[assembly: AssemblyDescription("ASCOM Focuser driver for Meade.net")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("cjdawson.com")] +[assembly: AssemblyProduct("ASCOM Focuser driver for Meade.net")] +[assembly: AssemblyCopyright("Copyright © 2019 cjdawson.com")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(true)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4ad7a6d4-6d54-4a9a-bbf3-895353e318f8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +// +// TODO - Set your driver's version here +[assembly: AssemblyVersion("0.4.0.0")] +[assembly: AssemblyFileVersion("0.4.0.0")] diff --git a/Meade.net.focuser/Properties/Resources.Designer.cs b/Meade.net.focuser/Properties/Resources.Designer.cs new file mode 100644 index 0000000..67f90ec --- /dev/null +++ b/Meade.net.focuser/Properties/Resources.Designer.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ASCOM.Meade.net.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ASCOM.Meade.net.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ASCOM { + get { + object obj = ResourceManager.GetObject("ASCOM", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + internal static System.Drawing.Icon DefaultIcon { + get { + object obj = ResourceManager.GetObject("DefaultIcon", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + } +} diff --git a/Meade.net.focuser/Properties/Resources.resx b/Meade.net.focuser/Properties/Resources.resx new file mode 100644 index 0000000..e522d9e --- /dev/null +++ b/Meade.net.focuser/Properties/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\ASCOM.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\ASCOM.ico;System.Drawing.Icon, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/Meade.net.focuser/Properties/Settings.Designer.cs b/Meade.net.focuser/Properties/Settings.Designer.cs new file mode 100644 index 0000000..f1cc444 --- /dev/null +++ b/Meade.net.focuser/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ASCOM.Meade.net.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Meade.net.focuser/Properties/Settings.settings b/Meade.net.focuser/Properties/Settings.settings new file mode 100644 index 0000000..8e615f2 --- /dev/null +++ b/Meade.net.focuser/Properties/Settings.settings @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Meade.net.focuser/ReadMe.htm b/Meade.net.focuser/ReadMe.htm new file mode 100644 index 0000000..d0e812e --- /dev/null +++ b/Meade.net.focuser/ReadMe.htm @@ -0,0 +1,147 @@ + + + + + Untitled Document + + + + + + + + + + + +
+

ASCOM Focuser Driver (C#)

+
+



+

+

You have just created the skeleton of an ASCOM +Focuser driver in C#. It produces an in-process +(assembly) based driver.

+
+

Prior to developing your first driver, please +familiarize yourself with the developer +information we've provided at the ASCOM Initiative web site +(internet required). +

+

You must do the following in order to complete +your implementation:

+
    +
  1. Switch to the Debug configuration + and build the template now. It should build without errors. +

    +
  2. Add a test project to the + solution. There are templates that can be used to add either a + console or a Windows Forms application:

    +
+
    +
  • Select the ASCOM + Test Forms App (CS) or ASCOM + Test Console App (CS) template.

    +
  • Set a name for the test + application and click on OK.

    +
  • In the Wizard: set the same device + type and model name as for the driver and select Create to build the + test project.

    +
  • Set the Test Application to Run at + Startup.

    +
  • Click on Debug and the test + application should run. You should be able to select your + application in the chooser. Selecting Properties should show the + default setup dialog for your driver.

    +
  • Trying to continue will generate + errors because the additional properties have not been implemented.

    +
+
    +
  1. Go through the Driver.cs file and + replace the System.NotImplemented exceptions with code to implement + your driver's functionality. See the ASCOM IFocuserV3 + spec. If a property or method is not implemented in your driver the + System.NotImplemented exception must be replaced by an + ASCOM.PropertyNotImplemented or ASCOM.MethodNotImplemented + exception.

    +
  2. Customize the Setup Dialog (SetupDialogForm) to provide the + settings and other controls for your driver. You can bind settings + directly to controls on your dialog form, there's no need to manage + settings manually. A custom Settings class takes care of managing + your settings behind the scenes. +

    +
+

Notes:

+
    +
  • Successfully building the driver, + as well as using regasm + on the assembly, registers it for both COM and ASCOM (the Chooser). + See the code in the ASCOM Registration region of Driver.vb. +

    +
  • Doing a Clean for the project, as + well doing a regasm + -u on the assembly, unregisters it for both COM and ASCOM + (the Chooser). +

    +
  • Place a breakpoint in your driver class constructor, then + start debugging (go, F5). Your breakpoint will be hit when the test + application creates an instance of your driver (after selecting it + in the Chooser). You can now single step, examine variables, etc. + Please review the test application and make changes and additions to + activate various parts of your driver during debugging.

    +
  • The project's Debug configuration is already configured (The + test application creates an instance of your driver (after selecting + it in the Chooser). You can now single step, examine variables, etc. + Please review the test application and feel free to make changes and + additions to activate various parts of your driver during debugging. +

    +
+
+ + + + + + + +
+ + + + + +
+

ASCOM Initiative

+
+
+



+

+
+

The ASCOM Initiative consists of a group of astronomy software + developers and instrument vendors whose goals are to promote the + driver/client model and scripting automation. +

+

See the ASCOM + web site for more information. Please participate in the + ASCOM-Talk + Yahoo Group. +

+
+
+



+

+

+

+ + \ No newline at end of file diff --git a/Meade.net.focuser/Resources/ASCOM.bmp b/Meade.net.focuser/Resources/ASCOM.bmp new file mode 100644 index 0000000000000000000000000000000000000000..55516c7aaa3591604865d4cd18379f8f650c3104 GIT binary patch literal 3382 zcmeIwA#&YN425B3CLjUbgJ57$uqM1+gO+lZkut%6E<(#F6wORPc)-i=r?4!c_Vwp; zZ3Fx2>gm_x54#?Zw`LE_etx;JyM1xL@%<_@PLnpcGPUluKQDjn-|L&12NJySLSxLr z;9wF7q0kt!G&lm>q)=$g*LR%^B!og^%$Qm^0tune7=F^=2qc6;W6U^h9D#&TXpDvN zo8~x1LMSw5m*!3e5<;OdyXkPr%u*^;D_frLmI06Zw(3st}a59h(3XRzqWFR3F8nf*xCj$wg(3oxIax#z*3XRz|Zzlr@q3r$RV$Z*g8j2NO iuBTlt#|(_)(Q$tsX9dm*oE11La8}@~z*&L+tOB3FDm>c& literal 0 HcmV?d00001 diff --git a/Meade.net.focuser/app.config b/Meade.net.focuser/app.config new file mode 100644 index 0000000..56895a9 --- /dev/null +++ b/Meade.net.focuser/app.config @@ -0,0 +1,8 @@ + + + + +
+ + + diff --git a/Meade.net.sln b/Meade.net.sln new file mode 100644 index 0000000..4ca39e2 --- /dev/null +++ b/Meade.net.sln @@ -0,0 +1,73 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.136 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meade.net", "Meade.net\Meade.net.csproj", "{3689A2CB-94C5-4012-A5CF-7E7D1DD27143}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meade.net.Telescope", "Meade.net.Telescope\Meade.net.Telescope.csproj", "{64308775-BD4A-469C-BCAB-3ED830B811AF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meade.net.focuser", "Meade.net.focuser\Meade.net.focuser.csproj", "{A97E3AEC-F11D-49DA-B259-DE99DA813A86}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TelescopeTestConsole", "TelescopeTestConsole\TelescopeTestConsole.csproj", "{D5207217-61C7-4E94-8097-91DBACE57D2A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FocuserTestConsole", "FocuserTestConsole\FocuserTestConsole.csproj", "{AABC96B8-C462-4B3A-9B5F-2929E3CB7A49}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestConsoles", "TestConsoles", "{BF650D97-AF98-4638-9C55-21311C6D88DA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3689A2CB-94C5-4012-A5CF-7E7D1DD27143}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3689A2CB-94C5-4012-A5CF-7E7D1DD27143}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3689A2CB-94C5-4012-A5CF-7E7D1DD27143}.Debug|x86.ActiveCfg = Debug|Any CPU + {3689A2CB-94C5-4012-A5CF-7E7D1DD27143}.Debug|x86.Build.0 = Debug|Any CPU + {3689A2CB-94C5-4012-A5CF-7E7D1DD27143}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3689A2CB-94C5-4012-A5CF-7E7D1DD27143}.Release|Any CPU.Build.0 = Release|Any CPU + {3689A2CB-94C5-4012-A5CF-7E7D1DD27143}.Release|x86.ActiveCfg = Release|Any CPU + {3689A2CB-94C5-4012-A5CF-7E7D1DD27143}.Release|x86.Build.0 = Release|Any CPU + {64308775-BD4A-469C-BCAB-3ED830B811AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64308775-BD4A-469C-BCAB-3ED830B811AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64308775-BD4A-469C-BCAB-3ED830B811AF}.Debug|x86.ActiveCfg = Debug|Any CPU + {64308775-BD4A-469C-BCAB-3ED830B811AF}.Debug|x86.Build.0 = Debug|Any CPU + {64308775-BD4A-469C-BCAB-3ED830B811AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64308775-BD4A-469C-BCAB-3ED830B811AF}.Release|Any CPU.Build.0 = Release|Any CPU + {64308775-BD4A-469C-BCAB-3ED830B811AF}.Release|x86.ActiveCfg = Release|Any CPU + {64308775-BD4A-469C-BCAB-3ED830B811AF}.Release|x86.Build.0 = Release|Any CPU + {A97E3AEC-F11D-49DA-B259-DE99DA813A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A97E3AEC-F11D-49DA-B259-DE99DA813A86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A97E3AEC-F11D-49DA-B259-DE99DA813A86}.Debug|x86.ActiveCfg = Debug|Any CPU + {A97E3AEC-F11D-49DA-B259-DE99DA813A86}.Debug|x86.Build.0 = Debug|Any CPU + {A97E3AEC-F11D-49DA-B259-DE99DA813A86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A97E3AEC-F11D-49DA-B259-DE99DA813A86}.Release|Any CPU.Build.0 = Release|Any CPU + {A97E3AEC-F11D-49DA-B259-DE99DA813A86}.Release|x86.ActiveCfg = Release|Any CPU + {A97E3AEC-F11D-49DA-B259-DE99DA813A86}.Release|x86.Build.0 = Release|Any CPU + {D5207217-61C7-4E94-8097-91DBACE57D2A}.Debug|Any CPU.ActiveCfg = Debug|x86 + {D5207217-61C7-4E94-8097-91DBACE57D2A}.Debug|x86.ActiveCfg = Debug|x86 + {D5207217-61C7-4E94-8097-91DBACE57D2A}.Debug|x86.Build.0 = Debug|x86 + {D5207217-61C7-4E94-8097-91DBACE57D2A}.Release|Any CPU.ActiveCfg = Release|x86 + {D5207217-61C7-4E94-8097-91DBACE57D2A}.Release|x86.ActiveCfg = Release|x86 + {D5207217-61C7-4E94-8097-91DBACE57D2A}.Release|x86.Build.0 = Release|x86 + {AABC96B8-C462-4B3A-9B5F-2929E3CB7A49}.Debug|Any CPU.ActiveCfg = Debug|x86 + {AABC96B8-C462-4B3A-9B5F-2929E3CB7A49}.Debug|x86.ActiveCfg = Debug|x86 + {AABC96B8-C462-4B3A-9B5F-2929E3CB7A49}.Debug|x86.Build.0 = Debug|x86 + {AABC96B8-C462-4B3A-9B5F-2929E3CB7A49}.Release|Any CPU.ActiveCfg = Release|x86 + {AABC96B8-C462-4B3A-9B5F-2929E3CB7A49}.Release|x86.ActiveCfg = Release|x86 + {AABC96B8-C462-4B3A-9B5F-2929E3CB7A49}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D5207217-61C7-4E94-8097-91DBACE57D2A} = {BF650D97-AF98-4638-9C55-21311C6D88DA} + {AABC96B8-C462-4B3A-9B5F-2929E3CB7A49} = {BF650D97-AF98-4638-9C55-21311C6D88DA} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3C0509DC-C7F5-48DC-920D-DCFD9C879BD2} + EndGlobalSection +EndGlobal diff --git a/Meade.net/ASCOM.ico b/Meade.net/ASCOM.ico new file mode 100644 index 0000000000000000000000000000000000000000..9bf8f4110f20bfdde720ab5e04def9b01b414268 GIT binary patch literal 125783 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 literal 0 HcmV?d00001 diff --git a/Meade.net/ClassFactory.cs b/Meade.net/ClassFactory.cs new file mode 100644 index 0000000..1626f5c --- /dev/null +++ b/Meade.net/ClassFactory.cs @@ -0,0 +1,244 @@ +using System; +using System.Runtime.InteropServices; +using System.Collections; + +namespace ASCOM.Meade.net +{ + + #region C# Definition of IClassFactory + // + // Provide a definition of theCOM IClassFactory interface. + // + [ + ComImport, // This interface originated from COM. + ComVisible(false), // Must not be exposed to COM!!! + InterfaceType(ComInterfaceType.InterfaceIsIUnknown), // Indicate that this interface is not IDispatch-based. + Guid("00000001-0000-0000-C000-000000000046") // This GUID is the actual GUID of IClassFactory. + ] + public interface IClassFactory + { + void CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject); + void LockServer(bool fLock); + } + #endregion + + // + // Universal ClassFactory. Given a type as a parameter of the + // constructor, it implements IClassFactory for any interface + // that the class implements. Magic!!! + // + public class ClassFactory : IClassFactory + { + + #region Access to ole32.dll functions for class factories + + // Define two common GUID objects for public usage. + public static Guid IID_IUnknown = new Guid("{00000000-0000-0000-C000-000000000046}"); + public static Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}"); + + [Flags] + enum CLSCTX : uint + { + CLSCTX_INPROC_SERVER = 0x1, + CLSCTX_INPROC_HANDLER = 0x2, + CLSCTX_LOCAL_SERVER = 0x4, + CLSCTX_INPROC_SERVER16 = 0x8, + CLSCTX_REMOTE_SERVER = 0x10, + CLSCTX_INPROC_HANDLER16 = 0x20, + CLSCTX_RESERVED1 = 0x40, + CLSCTX_RESERVED2 = 0x80, + CLSCTX_RESERVED3 = 0x100, + CLSCTX_RESERVED4 = 0x200, + CLSCTX_NO_CODE_DOWNLOAD = 0x400, + CLSCTX_RESERVED5 = 0x800, + CLSCTX_NO_CUSTOM_MARSHAL = 0x1000, + CLSCTX_ENABLE_CODE_DOWNLOAD = 0x2000, + CLSCTX_NO_FAILURE_LOG = 0x4000, + CLSCTX_DISABLE_AAA = 0x8000, + CLSCTX_ENABLE_AAA = 0x10000, + CLSCTX_FROM_DEFAULT_CONTEXT = 0x20000, + CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, + CLSCTX_SERVER = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, + CLSCTX_ALL = CLSCTX_SERVER | CLSCTX_INPROC_HANDLER + } + + [Flags] + enum REGCLS : uint + { + REGCLS_SINGLEUSE = 0, + REGCLS_MULTIPLEUSE = 1, + REGCLS_MULTI_SEPARATE = 2, + REGCLS_SUSPENDED = 4, + REGCLS_SURROGATE = 8 + } + // + // CoRegisterClassObject() is used to register a Class Factory + // into COM's internal table of Class Factories. + // + [DllImport("ole32.dll")] + static extern int CoRegisterClassObject( + [In] ref Guid rclsid, + [MarshalAs(UnmanagedType.IUnknown)] object pUnk, + uint dwClsContext, + uint flags, + out uint lpdwRegister); + // + // Called by a COM EXE Server that can register multiple class objects + // to inform COM about all registered classes, and permits activation + // requests for those class objects. + // This function causes OLE to inform the SCM about all the registered + // classes, and begins letting activation requests into the server process. + // + [DllImport("ole32.dll")] + static extern int CoResumeClassObjects(); + // + // Prevents any new activation requests from the SCM on all class objects + // registered within the process. Even though a process may call this API, + // the process still must call CoRevokeClassObject for each CLSID it has + // registered, in the apartment it registered in. + // + [DllImport("ole32.dll")] + static extern int CoSuspendClassObjects(); + // + // CoRevokeClassObject() is used to unregister a Class Factory + // from COM's internal table of Class Factories. + // + [DllImport("ole32.dll")] + static extern int CoRevokeClassObject(uint dwRegister); + #endregion + + #region Constructor and Private ClassFactory Data + + protected Type m_ClassType; + protected Guid m_ClassId; + protected ArrayList m_InterfaceTypes; + protected uint m_ClassContext; + protected uint m_Flags; + protected UInt32 m_locked = 0; + protected uint m_Cookie; + protected string m_progid; + + public ClassFactory(Type type) + { + if (type == null) + throw new ArgumentNullException("type"); + m_ClassType = type; + + //PWGS Get the ProgID from the MetaData + m_progid = Marshal.GenerateProgIdForType(type); + m_ClassId = Marshal.GenerateGuidForType(type); // Should be nailed down by [Guid(...)] + m_ClassContext = (uint)CLSCTX.CLSCTX_LOCAL_SERVER; // Default + m_Flags = (uint)REGCLS.REGCLS_MULTIPLEUSE | // Default + (uint)REGCLS.REGCLS_SUSPENDED; + m_InterfaceTypes = new ArrayList(); + foreach (Type T in type.GetInterfaces()) // Save all of the implemented interfaces + m_InterfaceTypes.Add(T); + } + + #endregion + + #region Common ClassFactory Methods + public uint ClassContext + { + get { return m_ClassContext; } + set { m_ClassContext = value; } + } + + public Guid ClassId + { + get { return m_ClassId; } + set { m_ClassId = value; } + } + + public uint Flags + { + get { return m_Flags; } + set { m_Flags = value; } + } + + public bool RegisterClassObject() + { + // Register the class factory + int i = CoRegisterClassObject + ( + ref m_ClassId, + this, + m_ClassContext, + m_Flags, + out m_Cookie + ); + return (i == 0); + } + + public bool RevokeClassObject() + { + int i = CoRevokeClassObject(m_Cookie); + return (i == 0); + } + + public static bool ResumeClassObjects() + { + int i = CoResumeClassObjects(); + return (i == 0); + } + + public static bool SuspendClassObjects() + { + int i = CoSuspendClassObjects(); + return (i == 0); + } + #endregion + + #region IClassFactory Implementations + // + // Implement creation of the type and interface. + // + void IClassFactory.CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject) + { + IntPtr nullPtr = new IntPtr(0); + ppvObject = nullPtr; + + // + // Handle specific requests for implemented interfaces + // + foreach (Type iType in m_InterfaceTypes) + { + if (riid == Marshal.GenerateGuidForType(iType)) + { + ppvObject = Marshal.GetComInterfaceForObject(Activator.CreateInstance(m_ClassType), iType); + return; + } + } + // + // Handle requests for IDispatch or IUnknown on the class + // + if (riid == IID_IDispatch) + { + ppvObject = Marshal.GetIDispatchForObject(Activator.CreateInstance(m_ClassType)); + return; + } + else if (riid == IID_IUnknown) + { + ppvObject = Marshal.GetIUnknownForObject(Activator.CreateInstance(m_ClassType)); + } + else + { + // + // Oops, some interface that the class doesn't implement + // + throw new COMException("No interface", unchecked((int)0x80004002)); + } + } + + void IClassFactory.LockServer(bool bLock) + { + if (bLock) + Server.CountLock(); + else + Server.UncountLock(); + // Always attempt to see if we need to shutdown this server application. + Server.ExitIf(); + } + #endregion + } +} diff --git a/Meade.net/GarbageCollection.cs b/Meade.net/GarbageCollection.cs new file mode 100644 index 0000000..99aba52 --- /dev/null +++ b/Meade.net/GarbageCollection.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading; + +namespace ASCOM.Meade.net +{ + /// + /// Summary description for GarbageCollection. + /// + class GarbageCollection + { + protected bool m_bContinueThread; + protected bool m_GCWatchStopped; + protected int m_iInterval; + protected ManualResetEvent m_EventThreadEnded; + + public GarbageCollection(int iInterval) + { + m_bContinueThread = true; + m_GCWatchStopped = false; + m_iInterval = iInterval; + m_EventThreadEnded = new ManualResetEvent(false); + } + + public void GCWatch() + { + // Pause for a moment to provide a delay to make threads more apparent. + while (ContinueThread()) + { + GC.Collect(); + Thread.Sleep(m_iInterval); + } + m_EventThreadEnded.Set(); + } + + protected bool ContinueThread() + { + lock (this) + { + return m_bContinueThread; + } + } + + public void StopThread() + { + lock (this) + { + m_bContinueThread = false; + } + } + + public void WaitForThreadToStop() + { + m_EventThreadEnded.WaitOne(); + m_EventThreadEnded.Reset(); + } + } +} diff --git a/Meade.net/LocalServer.cs b/Meade.net/LocalServer.cs new file mode 100644 index 0000000..a4fc2c2 --- /dev/null +++ b/Meade.net/LocalServer.cs @@ -0,0 +1,642 @@ +// +// ASCOM.Meade.net Local COM Server +// +// This is the core of a managed COM Local Server, capable of serving +// multiple instances of multiple interfaces, within a single +// executable. This implementes the equivalent functionality of VB6 +// which has been extensively used in ASCOM for drivers that provide +// multiple interfaces to multiple clients (e.g. Meade Telescope +// and Focuser) as well as hubs (e.g., POTH). +// +// Written by: Robert B. Denny (Version 1.0.1, 29-May-2007) +// Modified by Chris Rowland and Peter Simpson to allow use with multiple devices of the same type March 2011 +// +// +using System; +using System.IO; +using System.Windows.Forms; +using System.Drawing; +using System.Collections; +using System.Runtime.InteropServices; +using System.Reflection; +using ASCOM.Utilities; +using Microsoft.Win32; +using System.Text; +using System.Threading; +using System.Security.Principal; +using System.Diagnostics; +using ASCOM; + +namespace ASCOM.Meade.net +{ + public static class Server + { + + #region Access to kernel32.dll, user32.dll, and ole32.dll functions + [Flags] + enum CLSCTX : uint + { + CLSCTX_INPROC_SERVER = 0x1, + CLSCTX_INPROC_HANDLER = 0x2, + CLSCTX_LOCAL_SERVER = 0x4, + CLSCTX_INPROC_SERVER16 = 0x8, + CLSCTX_REMOTE_SERVER = 0x10, + CLSCTX_INPROC_HANDLER16 = 0x20, + CLSCTX_RESERVED1 = 0x40, + CLSCTX_RESERVED2 = 0x80, + CLSCTX_RESERVED3 = 0x100, + CLSCTX_RESERVED4 = 0x200, + CLSCTX_NO_CODE_DOWNLOAD = 0x400, + CLSCTX_RESERVED5 = 0x800, + CLSCTX_NO_CUSTOM_MARSHAL = 0x1000, + CLSCTX_ENABLE_CODE_DOWNLOAD = 0x2000, + CLSCTX_NO_FAILURE_LOG = 0x4000, + CLSCTX_DISABLE_AAA = 0x8000, + CLSCTX_ENABLE_AAA = 0x10000, + CLSCTX_FROM_DEFAULT_CONTEXT = 0x20000, + CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, + CLSCTX_SERVER = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, + CLSCTX_ALL = CLSCTX_SERVER | CLSCTX_INPROC_HANDLER + } + + [Flags] + enum COINIT : uint + { + /// Initializes the thread for multi-threaded object concurrency. + COINIT_MULTITHREADED = 0x0, + /// Initializes the thread for apartment-threaded object concurrency. + COINIT_APARTMENTTHREADED = 0x2, + /// Disables DDE for Ole1 support. + COINIT_DISABLE_OLE1DDE = 0x4, + /// Trades memory for speed. + COINIT_SPEED_OVER_MEMORY = 0x8 + } + + [Flags] + enum REGCLS : uint + { + REGCLS_SINGLEUSE = 0, + REGCLS_MULTIPLEUSE = 1, + REGCLS_MULTI_SEPARATE = 2, + REGCLS_SUSPENDED = 4, + REGCLS_SURROGATE = 8 + } + + + // CoInitializeEx() can be used to set the apartment model + // of individual threads. + [DllImport("ole32.dll")] + static extern int CoInitializeEx(IntPtr pvReserved, uint dwCoInit); + + // CoUninitialize() is used to uninitialize a COM thread. + [DllImport("ole32.dll")] + static extern void CoUninitialize(); + + // PostThreadMessage() allows us to post a Windows Message to + // a specific thread (identified by its thread id). + // We will need this API to post a WM_QUIT message to the main + // thread in order to terminate this application. + [DllImport("user32.dll")] + static extern bool PostThreadMessage(uint idThread, uint Msg, UIntPtr wParam, + IntPtr lParam); + + // GetCurrentThreadId() allows us to obtain the thread id of the + // calling thread. This allows us to post the WM_QUIT message to + // the main thread. + [DllImport("kernel32.dll")] + static extern uint GetCurrentThreadId(); + #endregion + + #region Private Data + private static int objsInUse; // Keeps a count on the total number of objects alive. + private static int serverLocks; // Keeps a lock count on this application. + private static frmMain s_MainForm = null; // Reference to our main form + private static ArrayList s_ComObjectAssys; // Dynamically loaded assemblies containing served COM objects + private static ArrayList s_ComObjectTypes; // Served COM object types + private static ArrayList s_ClassFactories; // Served COM object class factories + private static string s_appId = "{4e68ec46-5ffc-49e7-b298-38a548df0bfd}"; // Our AppId + private static readonly Object lockObject = new object(); + #endregion + + // This property returns the main thread's id. + public static uint MainThreadId { get; private set; } // Stores the main thread's thread id. + + // Used to tell if started by COM or manually + public static bool StartedByCOM { get; private set; } // True if server started by COM (-embedding) + + + #region Server Lock, Object Counting, and AutoQuit on COM startup + // Returns the total number of objects alive currently. + public static int ObjectsCount + { + get + { + lock (lockObject) + { + return objsInUse; + } + } + } + + // This method performs a thread-safe incrementation of the objects count. + public static int CountObject() + { + // Increment the global count of objects. + return Interlocked.Increment(ref objsInUse); + } + + // This method performs a thread-safe decrementation the objects count. + public static int UncountObject() + { + // Decrement the global count of objects. + return Interlocked.Decrement(ref objsInUse); + } + + // Returns the current server lock count. + public static int ServerLockCount + { + get + { + lock (lockObject) + { + return serverLocks; + } + } + } + + // This method performs a thread-safe incrementation the + // server lock count. + public static int CountLock() + { + // Increment the global lock count of this server. + return Interlocked.Increment(ref serverLocks); + } + + // This method performs a thread-safe decrementation the + // server lock count. + public static int UncountLock() + { + // Decrement the global lock count of this server. + return Interlocked.Decrement(ref serverLocks); + } + + // AttemptToTerminateServer() will check to see if the objects count and the server + // lock count have both dropped to zero. + // + // If so, and if we were started by COM, we post a WM_QUIT message to the main thread's + // message loop. This will cause the message loop to exit and hence the termination + // of this application. If hand-started, then just trace that it WOULD exit now. + // + public static void ExitIf() + { + lock (lockObject) + { + if ((ObjectsCount <= 0) && (ServerLockCount <= 0)) + { + if (StartedByCOM) + { + UIntPtr wParam = new UIntPtr(0); + IntPtr lParam = new IntPtr(0); + PostThreadMessage(MainThreadId, 0x0012, wParam, lParam); + } + } + } + } + #endregion + + // ----------------- + // PRIVATE FUNCTIONS + // ----------------- + + #region Dynamic Driver Assembly Loader + // + // Load the assemblies that contain the classes that we will serve + // via COM. These will be located in the same folder as + // our executable. + // + private static bool LoadComObjectAssemblies() + { + s_ComObjectAssys = new ArrayList(); + s_ComObjectTypes = new ArrayList(); + + // put everything into one folder, the same as the server. + string assyPath = Assembly.GetEntryAssembly().Location; + assyPath = Path.GetDirectoryName(assyPath); + + DirectoryInfo d = new DirectoryInfo(assyPath); + foreach (FileInfo fi in d.GetFiles("*.dll")) + { + string aPath = fi.FullName; + // + // First try to load the assembly and get the types for + // the class and the class factory. If this doesn't work ???? + // + try + { + Assembly so = Assembly.LoadFrom(aPath); + //PWGS Get the types in the assembly + Type[] types = so.GetTypes(); + foreach (Type type in types) + { + // PWGS Now checks the type rather than the assembly + // Check to see if the type has the ServedClassName attribute, only use it if it does. + MemberInfo info = type; + + object[] attrbutes = info.GetCustomAttributes(typeof(ServedClassNameAttribute), false); + if (attrbutes.Length > 0) + { + //MessageBox.Show("Adding Type: " + type.Name + " " + type.FullName); + s_ComObjectTypes.Add(type); //PWGS - much simpler + s_ComObjectAssys.Add(so); + } + } + } + catch (BadImageFormatException) + { + // Probably an attempt to load a Win32 DLL (i.e. not a .net assembly) + // Just swallow the exception and continue to the next item. + continue; + } + catch (Exception e) + { + MessageBox.Show("Failed to load served COM class assembly " + fi.Name + " - " + e.Message, + "Meade.net", MessageBoxButtons.OK, MessageBoxIcon.Stop); + return false; + } + + } + return true; + } + #endregion + + #region COM Registration and Unregistration + // + // Test if running elevated + // + private static bool IsAdministrator + { + get + { + WindowsIdentity i = WindowsIdentity.GetCurrent(); + WindowsPrincipal p = new WindowsPrincipal(i); + return p.IsInRole(WindowsBuiltInRole.Administrator); + } + } + + // + // Elevate by re-running ourselves with elevation dialog + // + private static void ElevateSelf(string arg) + { + ProcessStartInfo si = new ProcessStartInfo(); + si.Arguments = arg; + si.WorkingDirectory = Environment.CurrentDirectory; + si.FileName = Application.ExecutablePath; + si.Verb = "runas"; + try { Process.Start(si); } + catch (System.ComponentModel.Win32Exception) + { + MessageBox.Show("The Meade.net was not " + (arg == "/register" ? "registered" : "unregistered") + + " because you did not allow it.", "Meade.net", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString(), "Meade.net", MessageBoxButtons.OK, MessageBoxIcon.Stop); + } + return; + } + + // + // Do everything to register this for COM. Never use REGASM on + // this exe assembly! It would create InProcServer32 entries + // which would prevent proper activation! + // + // Using the list of COM object types generated during dynamic + // assembly loading, it registers each one for COM as served by our + // exe/local server, as well as registering it for ASCOM. It also + // adds DCOM info for the local server itself, so it can be activated + // via an outboiud connection from TheSky. + // + private static void RegisterObjects() + { + if (!IsAdministrator) + { + ElevateSelf("/register"); + return; + } + // + // If reached here, we're running elevated + // + + Assembly assy = Assembly.GetExecutingAssembly(); + Attribute attr = Attribute.GetCustomAttribute(assy, typeof(AssemblyTitleAttribute)); + string assyTitle = ((AssemblyTitleAttribute)attr).Title; + attr = Attribute.GetCustomAttribute(assy, typeof(AssemblyDescriptionAttribute)); + string assyDescription = ((AssemblyDescriptionAttribute)attr).Description; + + // + // Local server's DCOM/AppID information + // + try + { + // + // HKCR\APPID\appid + // + using (RegistryKey key = Registry.ClassesRoot.CreateSubKey("APPID\\" + s_appId)) + { + key.SetValue(null, assyDescription); + key.SetValue("AppID", s_appId); + key.SetValue("AuthenticationLevel", 1, RegistryValueKind.DWord); + } + // + // HKCR\APPID\exename.ext + // + using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(string.Format("APPID\\{0}", + Application.ExecutablePath.Substring(Application.ExecutablePath.LastIndexOf('\\') + 1)))) + { + key.SetValue("AppID", s_appId); + } + } + catch (Exception ex) + { + MessageBox.Show("Error while registering the server:\n" + ex.ToString(), + "Meade.net", MessageBoxButtons.OK, MessageBoxIcon.Stop); + return; + } + finally + { + } + + // + // For each of the driver assemblies + // + foreach (Type type in s_ComObjectTypes) + { + bool bFail = false; + try + { + // + // HKCR\CLSID\clsid + // + string clsid = Marshal.GenerateGuidForType(type).ToString("B"); + string progid = Marshal.GenerateProgIdForType(type); + //PWGS Generate device type from the Class name + string deviceType = type.Name; + + using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(string.Format("CLSID\\{0}", clsid))) + { + key.SetValue(null, progid); // Could be assyTitle/Desc??, but .NET components show ProgId here + key.SetValue("AppId", s_appId); + using (RegistryKey key2 = key.CreateSubKey("Implemented Categories")) + { + key2.CreateSubKey("{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}"); + } + using (RegistryKey key2 = key.CreateSubKey("ProgId")) + { + key2.SetValue(null, progid); + } + key.CreateSubKey("Programmable"); + using (RegistryKey key2 = key.CreateSubKey("LocalServer32")) + { + key2.SetValue(null, Application.ExecutablePath); + } + } + // + // HKCR\CLSID\progid + // + using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(progid)) + { + key.SetValue(null, assyTitle); + using (RegistryKey key2 = key.CreateSubKey("CLSID")) + { + key2.SetValue(null, clsid); + } + } + // + // ASCOM + // + assy = type.Assembly; + + // Pull the display name from the ServedClassName attribute. + attr = Attribute.GetCustomAttribute(type, typeof(ServedClassNameAttribute)); //PWGS Changed to search type for attribute rather than assembly + string chooserName = ((ServedClassNameAttribute)attr).DisplayName ?? "MultiServer"; + using (var P = new ASCOM.Utilities.Profile()) + { + P.DeviceType = deviceType; + P.Register(progid, chooserName); + } + } + catch (Exception ex) + { + MessageBox.Show("Error while registering the server:\n" + ex.ToString(), + "Meade.net", MessageBoxButtons.OK, MessageBoxIcon.Stop); + bFail = true; + } + finally + { + } + if (bFail) break; + } + } + + // + // Remove all traces of this from the registry. + // + // **TODO** If the above does AppID/DCOM stuff, this would have + // to remove that stuff too. + // + private static void UnregisterObjects() + { + if (!IsAdministrator) + { + ElevateSelf("/unregister"); + return; + } + + // + // Local server's DCOM/AppID information + // + Registry.ClassesRoot.DeleteSubKey(string.Format("APPID\\{0}", s_appId), false); + Registry.ClassesRoot.DeleteSubKey(string.Format("APPID\\{0}", + Application.ExecutablePath.Substring(Application.ExecutablePath.LastIndexOf('\\') + 1)), false); + + // + // For each of the driver assemblies + // + foreach (Type type in s_ComObjectTypes) + { + string clsid = Marshal.GenerateGuidForType(type).ToString("B"); + string progid = Marshal.GenerateProgIdForType(type); + string deviceType = type.Name; + // + // Best efforts + // + // + // HKCR\progid + // + Registry.ClassesRoot.DeleteSubKey(String.Format("{0}\\CLSID", progid), false); + Registry.ClassesRoot.DeleteSubKey(progid, false); + // + // HKCR\CLSID\clsid + // + Registry.ClassesRoot.DeleteSubKey(String.Format("CLSID\\{0}\\Implemented Categories\\{{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}}", clsid), false); + Registry.ClassesRoot.DeleteSubKey(String.Format("CLSID\\{0}\\Implemented Categories", clsid), false); + Registry.ClassesRoot.DeleteSubKey(String.Format("CLSID\\{0}\\ProgId", clsid), false); + Registry.ClassesRoot.DeleteSubKey(String.Format("CLSID\\{0}\\LocalServer32", clsid), false); + Registry.ClassesRoot.DeleteSubKey(String.Format("CLSID\\{0}\\Programmable", clsid), false); + Registry.ClassesRoot.DeleteSubKey(String.Format("CLSID\\{0}", clsid), false); + try + { + // + // ASCOM + // + using (var P = new ASCOM.Utilities.Profile()) + { + P.DeviceType = deviceType; + P.Unregister(progid); + } + } + catch (Exception) { } + } + } + #endregion + + #region Class Factory Support + // + // On startup, we register the class factories of the COM objects + // that we serve. This requires the class facgtory name to be + // equal to the served class name + "ClassFactory". + // + private static bool RegisterClassFactories() + { + s_ClassFactories = new ArrayList(); + foreach (Type type in s_ComObjectTypes) + { + ClassFactory factory = new ClassFactory(type); // Use default context & flags + s_ClassFactories.Add(factory); + if (!factory.RegisterClassObject()) + { + MessageBox.Show("Failed to register class factory for " + type.Name, + "Meade.net", MessageBoxButtons.OK, MessageBoxIcon.Stop); + return false; + } + } + ClassFactory.ResumeClassObjects(); // Served objects now go live + return true; + } + + private static void RevokeClassFactories() + { + ClassFactory.SuspendClassObjects(); // Prevent race conditions + foreach (ClassFactory factory in s_ClassFactories) + factory.RevokeClassObject(); + } + #endregion + + #region Command Line Arguments + // + // ProcessArguments() will process the command-line arguments + // If the return value is true, we carry on and start this application. + // If the return value is false, we terminate this application immediately. + // + private static bool ProcessArguments(string[] args) + { + bool bRet = true; + + // + //**TODO** -Embedding is "ActiveX start". Prohibit non_AX starting? + // + if (args.Length > 0) + { + + switch (args[0].ToLower()) + { + case "-embedding": + StartedByCOM = true; // Indicate COM started us + break; + + case "-register": + case @"/register": + case "-regserver": // Emulate VB6 + case @"/regserver": + RegisterObjects(); // Register each served object + bRet = false; + break; + + case "-unregister": + case @"/unregister": + case "-unregserver": // Emulate VB6 + case @"/unregserver": + UnregisterObjects(); //Unregister each served object + bRet = false; + break; + + default: + MessageBox.Show("Unknown argument: " + args[0] + "\nValid are : -register, -unregister and -embedding", + "Meade.net", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + break; + } + } + else + StartedByCOM = false; + + return bRet; + } + #endregion + + #region SERVER ENTRY POINT (main) + // + // ================== + // SERVER ENTRY POINT + // ================== + // + [STAThread] + static void Main(string[] args) + { + if (!LoadComObjectAssemblies()) return; // Load served COM class assemblies, get types + + if (!ProcessArguments(args)) return; // Register/Unregister + + // Initialize critical member variables. + objsInUse = 0; + serverLocks = 0; + MainThreadId = GetCurrentThreadId(); + Thread.CurrentThread.Name = "Main Thread"; + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + s_MainForm = new frmMain(); + if (StartedByCOM) s_MainForm.WindowState = FormWindowState.Minimized; + + // Register the class factories of the served objects + RegisterClassFactories(); + + // Start up the garbage collection thread. + GarbageCollection GarbageCollector = new GarbageCollection(1000); + Thread GCThread = new Thread(new ThreadStart(GarbageCollector.GCWatch)); + GCThread.Name = "Garbage Collection Thread"; + GCThread.Start(); + + // + // Start the message loop. This serializes incoming calls to our + // served COM objects, making this act like the VB6 equivalent! + // + try + { + Application.Run(s_MainForm); + } + finally + { + // Revoke the class factories immediately. + // Don't wait until the thread has stopped before + // we perform revocation!!! + RevokeClassFactories(); + + // Now stop the Garbage Collector thread. + GarbageCollector.StopThread(); + GarbageCollector.WaitForThreadToStop(); + } + } + #endregion + } +} diff --git a/Meade.net/LocalServer.snk b/Meade.net/LocalServer.snk new file mode 100644 index 0000000000000000000000000000000000000000..97e44064e7bc6d608913a5a8542b39243e53f825 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097L4}G28oM$F0^<%6-cFiYYwSe?a7L$A- zPjs7(CKo#X44$udl=V0HdhSa|Y66!|Ee(v)u)x;tmy;IRJq0+T?30Vq3#N+7qYfU@ z9O=aZq`Jd&LLfG^7Q=v6%;?I7R*Ob~BNWW>wU0h|lZxR5RjYCU5hT%23xHk!l7y{w*Esa?cjU<{J?8%BZ&RTmS$T{Q8GL(z0_4No`a@f>bwzY>oTIZ?KKmt1 z%!cfuHpE9Qu{Tgt^*7ThFAo9E_{`1u)ALOuR?3|Kk%^P@5fo{=p-yZy|a-1WwMiN94P<1|k z=QFJxPSyMkCSL)@b@w(o(6~ALWpYG_snMlk_o;5nI$DnQ*Tg`u@w*iH%R;7PGk#FC z<^Xcp`x#c7`sRstdut^$U+rB}H21X-*C+yMr+P&V#vP~GM$sJiz;Q!T#JQl9p%jDV zE9>eGU#kLv8q?h#9EtW9Y@0W@DvTwt;ax~FLk2K^;9_V;6Y(tQxcBNk$F{w_mnD!8 zyu%keSLp3=yW&LBho=(UyK%1d3#C|3h%I1j_nvwvd6bwG@H`nK?OC-O5be;t(wl3c i#j%9ZS$2MI$Iv1T + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {3689A2CB-94C5-4012-A5CF-7E7D1DD27143} + WinExe + Properties + ASCOM.Meade.net + ASCOM.Meade.net.Server + v4.7.1 + + + 2.0 + + + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + true + full + false + ..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + x86 + MinimumRecommendedRules.ruleset + false + + + pdbonly + true + ..\bin\Release\ + TRACE + prompt + 4 + x86 + false + + + true + + + LocalServer.snk + + + ASCOM.ico + + + + + + + + + + + + + + Form + + + frmMain.cs + + + + + + Designer + frmMain.cs + + + True + True + Resources.resx + + + + Form + + + SetupDialogForm.cs + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SetupDialogForm.cs + Designer + + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + + + + + + + \ No newline at end of file diff --git a/Meade.net/Properties/AssemblyInfo.cs b/Meade.net/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..af3da5d --- /dev/null +++ b/Meade.net/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ASCOM Meade.net server")] +[assembly: AssemblyDescription("ASCOM multi-interface server for Meade.net")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("cjdawson.com")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Copyright © 2019 cjdawson.com")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("0.4.0.0")] +[assembly: AssemblyFileVersion("0.4.0.0")] + +[assembly: ComVisibleAttribute(false)] diff --git a/Meade.net/Properties/Resources.Designer.cs b/Meade.net/Properties/Resources.Designer.cs new file mode 100644 index 0000000..0255290 --- /dev/null +++ b/Meade.net/Properties/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ASCOM.Meade.net.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ASCOM.Meade.net.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ASCOM { + get { + object obj = ResourceManager.GetObject("ASCOM", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/Meade.net/Properties/Resources.resx b/Meade.net/Properties/Resources.resx new file mode 100644 index 0000000..f4cef88 --- /dev/null +++ b/Meade.net/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\ASCOM.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/Meade.net/ReadMe.htm b/Meade.net/ReadMe.htm new file mode 100644 index 0000000..0a8dc80 --- /dev/null +++ b/Meade.net/ReadMe.htm @@ -0,0 +1,666 @@ + + + + + Untitled Document + + + + + + + + + + + + +
+

+ ASCOM LocalServer (singleton) Host +

+
+

+
+
+ +

+

+ You have just created a local server (singleton) host for one or + more ASCOM driver classes. +

+
+

+ This project implements an ASCOM host server for one or more + driver classes in a single-instance executable. It can be used to + serve multiple instances of a single driver class (hub), provide + driver services for multiple devices (e.g., Telescope and Focuser) to + multiple applications and allow multiple devices of the same type to + be connected. In the latter scenario, the multiple driver classes + will often share one or more resources such as the serial connection + and a microcontroller in the combined device. From the client's + perspective, using the drivers served by the local server is exactly + the same as if the drivers are loaded into the client's process space + (in-proc servers). +

+

+ + NOTE: + + + Unless you are prepared to handle all of the timing issues that arise + when multiple clients are accessing the properties and methods of + your driver(s), stop now. Just because the local server serializes + the calls to your driver(s)' properties and methods does not mean + that there will be no timing or concurrency issues.
+  
For + example, suppose the hub serves instances of a Telescope driver. One + client sets the TargetRightAscension property, then another sets + TargetRightAscension to a different value, then the first client sets + TargetDeclination, then the first client calls SlewToTarget() + followed by the second client calling SlewToTarget(). Besides the + first client's slew command sending the scope to the wrong (and + possibly dangerous) coordinates, there is the problem of the second + client trying to slew a slewing scope. Local server drivers are + tricky to get right. There is no such thing as "ignorance is + bliss" here. + +
+

+

+ + This implementation has changed + from what was defined for Platform 5.5 as follows: + +

+

+ + The drivers are now installed in + the same folder as the local server executable. This makes deployment + cleaner because the whole driver can exist in a single folder + independently of other drivers. + +

+

+ + The ProgId and friendly name as + displayed by the Chooser are defined using attributes. This allows + driver dlls to be identified clearly and so avoids confusion with + other dlls that may be required such as interop dlls. + +

+

+ + Some changes have been made that + will facilitate generating multiple drivers of the same type. + +

+

+ + I've put some additional advice and + comments in the notes below in italics. + +

+

+ You're probably anxious to get going, but you really should read + through the Theory of Operation and + Detailed + Use and Deployment + below. +

+

You must do the following in order to complete your local server:

+
    +
  1. +

    + In the local server's project + properties, Application tab, change BOTH the AssemblyName and the + default assembly name to ASCOM.xxx (e.g., ASCOM.SuperScope). + This + may be done by default now. + +

    +
  2. +
  3. +

    + Add one or more driver skeleton + projects using the in-proc templates. You may use either the C# or + VB templates. Project name is not important (not used in ProgID) + choose something like TelescopeDriver. You will be changing the + substituted project name in these projects below. If you ensure that + the LocalServer and all the driver projects have the same NameSpace + e.g. ASCOM.SuperScope the renaming in section 6a will not be + required. +

    +
  4. +
  5. +

    + Develop and debug these driver + projects as normal in-process assemblies. This will be much simpler + because the driver and test code can be debugged in the same + process. +

    +
  6. +
  7. +

    + Build the LocalServer. +

    +
  8. +
  9. +

    + Set a reference to the local + server project in each of the driver skeleton + projects. +

    +
  10. +
  11. +

    In each skeleton driver project:

    +
      +
    1. +

      + Do a Find In Files for the + project name of the skeleton driver (e.g., TelescopeDriver) and + change it to match the project name of your local server (e.g. + SuperScope). You don't have to do this in the ReadMe.html file. + Everywhere else, however, is IMPORTANT. This sets the correct + namespace, progID, etc. If you're a bit more brave, you can use + Replace in Files. + This may not be needed if the correct + namespace and naming conventions have been followed when the + drivers and local server were generated. + +

      +
    2. +
    3. +

      + In project properties, + Application tab, change the assembly name to + ASCOM.localserverprojectname.drivertype, + (e.g., ASCOM.SuperScope.Telescope). +

      +
    4. +
    5. +

      + In project properties, + Application tab, click Assembly Information... +

      +
        +
      • +

        + Assure that Make assembly COM + visible is on (it should already be on). +

        +
      • +
      • +

        + Edit the Product Name to be the + "friendly name" of your driver as will be shown in the + Chooser. + Not used now, use the ServedClassName attribute + instead. + +

        +
      • +
      +
    6. +
    7. +

      + In project properties, Build tab, + turn off Register for COM Interop. +

      +
    8. +
    9. +

      + Modify the driver class declaration to inherit from + ReferenceCountedObjectBase. Examples:
      C#: +

      +
      +              public class Telescope :
      +              ReferenceCountedObjectBase,
      +              ITelescope
      +            
      +

      + VB: +

      +
      +              Public Class Telescope
      +              '==================================
      +              Inherits ReferenceCountedObjectBase
      +              Implements ITelescope
      +              '==================================
      +            
      +
    10. +
    11. +

      + In driver.cs/driver.vb, remove + the entire ASCOM Registration region +

      +
    12. +
    13. +

      + In driver.cs/driver.vb, remove + the private strings for driver ID and driver description. + They + may be needed internally, and if so should be set from the + associated attributes, ServedClassName for the description and + ProgId for the driver Id. + +

      +
    14. +
    15. +

      + Modify the class attributes by + adding the ServedClassName and ProgID attributes. The + ServedClassName attribute must be the friendly name shown as the + device name in the Chooser and the ProgId the progid of the driver + e.g. ASCOM.SuperScope.Telescope. The class header should look like + this: +

      +

      C#:

      +
      +              
      +                [Guid("0AE8B38D-10A1-4A8D-A5B7-1B050F74B48B")]  // set by the template
      +                [ProgId("ASCOM.SuperScope.Telescope")]
      +                [ServedClassName ("Super Scope Telescope")]
      +                [ClassInterface(ClassInterfaceType.None)]
      +                public class Telescope : ReferenceCountedObjectBase , ITelescope
      +              
      +            
      +
    16. +
    +
  12. +
+

+
+

+
    +
      +

      VB:

      +
      +          
      +            <Guid(“0AE8B38D-10A1-4A8D-A5B7-1B050F74B48B”)>
      +            <ProgId(“ASCOM.SuperScope.Telescope”)>
      +            <ServedClassName(“Super Scope Telescope”)>
      +            Public Class Telescope
      +            '==================================
      +            Inherits ReferenceCountedObjectBase
      +            Implements ITelescope
      +            '==================================
      +          
      +        
      +
    +
+

+ Add the following line to the driver + constructor, this sets the driver ID using the ProgId Attribute: +

+
    +
      +

      C#

      +
      +          
      +            s_csDriverID = Marshal.GenerateProgIdForType(this.GetType());
      +          
      +        
      +

      VB:

      +
      +          
      +            s_csDriverID = Marshal.GenerateProgIdForType(Me.GetType())
      +          
      +        
      +
    +
  1. +

    + Unless you're writing a + single-driver hub, you will have two or more driver types (e.g. + Telescope and Focuser) and thus two or more driver assembly projects + added. Presumably, these drivers need to share some resources (e.g. + a single COM port via Helper.Serial). + Put shared resources into + the SharedResources class provided + . There are some examples that + should give a clue, modify and delete these as required. +

    +
  2. +
  3. +

    + A shared serial port is already + provided (see SharedResources.cs) as SharedResources.SharedSerial + and it is an ASCOM Helper Serial object. You may wish to define + additional shared resources in static member variables with public + static accessor properties as is already done for SharedSerial. + Unfortunately, if you are a Visual Basic programmer, you will have + to make these additions in C#. +

    +
  4. +
  5. +

    + If you are writing a hub and don't + need the serial port, in SharedResources.cs you can remove the + public static SharedSerial property, the m_SharedSerial member in + the private data region, and the line in main that initializes it. + If you don't need any other shared resources for your hub, then you + can remove the SharedResources.cs file completely. +

    +
  6. +
  7. +

    + If you modified the LocalServer, + build it again now. This will refresh the stuff that's visible to + the drivers. +

    +
  8. +
  9. +

    + Build the driver skeletons to + verify that you got all of the namespace and other variable changes. +

    +
  10. +
  11. +

    + The local server dynamically loads the driver assemblies from + the same folder as the local server executable.
    +
    During + development, you'll need to add a post-build task to each of your + driver assembly projects which puts a copy of the driver assembly + into the local server executable folder. Here is an example: +

    +
        copy "$(TargetPath)" "$(SolutionDir)\SuperScope\$(OutDir)\$(TargetFileName)"
    +

    + This assumes that the server project is called “SuperScope”, + and handles using the debug or release build.
    + Note the quotes for + possible path elements with spaces in them. + An alternative is to + set the build path to the required destination instead of the + default path. + +

    +
  12. +
  13. +

    + + Make sure the drivers are + registered through the local server by running it with the /register + parameter, see below for details. + +

    +
  14. +
  15. +

    + IMPORTANT: + With a local server based driver (or hub) it is possible for + multiple clients to control the device(s). It is up to you to + safeguard against abuse. + The sort of thing that's needed is to + have a counter of the number of connections to a device, the + connection is only fully broken when the number of connections is + zero. You may also need code to prevent several drivers from talking + to the hardware at the same time, the lock pattern is useful for + that. + +

    +
  16. +
  17. +

    + You may want to add controls and/or status information to the + main form frmMain of the local server. Please resist the temptation + to turn the local server's main form into a graphical device control + panel. Instead, make a separate application that uses the served + driver(s). A driver is not a program! +

    +
  18. +
+

Notes

+
    +
  • +

    + The local server handles all of + the registration and unregistration for each of its served driver + classes, including the ASCOM Chooser info and the DCOM/AppID info + needed for activation from TheSky. By running the server from a + command line and giving /register or /unregister as the command line + option, it will register or unregister all served classes + (respectively). + Never use REGASM + on the local server executable! + + This can be done in the + Visual Studio IDE by setting the server project to run as startup + and setting the command line argument to /register in Debug – + Start Options. + +

    +
  • +
  • +

    + When you make the installer for + your local server based driver/hub, do not let it register the + executable for COM. Instead, have it activate the installed local + server with the /register option. +

    +
  • +
  • +

    + The ASCOM registration uses the ServedClassName attribute as + the friendly name that will show in the chooser and the ProgId + attribute as the driver Id. +

    +
  • +
  • +

    + The best deployment way is to install all the files in a + folder that's a sub folder of the main driver, so the SuperScope + driver files will be in the folder ...\ASCOM\Telescope\SuperScope. + This can be done in the Inno script by changing the DefaultDirName + like this:
    + DefaultDirName="{cf}\ASCOM\Telescope\SuperScope"
    then + the files can all be installed with DestDir: {app}; +

    +
  • +
+

+ Theory of Operation +

+

+ The local server is an executable which can provide multiple + instances of multiple drivers to multiple clients. This capability is + needed for two applications: +

+
    +
  • +

    + A hub, which allows multiple + clients to share a single device +

    +
  • +
  • +

    + A device which provides multiple services, such as a + telescope which has a focuser built-in where both the telescope and + focuser are controlled by the same serial connection and different + client programs need to control to the focuser and telescope. +

    +
  • +
+

+ By simply dropping suitably developed driver assemblies into the + same folder as the local server executable, the local server will + find them and register them for COM and ASCOM and serve any number of + instances of the drivers' interfaces to any number of client + programs. It does this by locating and loading the driver assemblies, + analysing them to detect their classes and interfaces, and + implementing a class factory that can create instances of them for + clients. +

+

+ A driver is an assembly which contains a class that implements + one of the ASCOM standard driver interfaces and inherits the + ReferenceCountedObjectBase class of the local server. Apart from + that, driver assemblies are identical to those that are used + in-process (DLL-type). The instructions above detail the steps needed + to convert an in-process driver into one that can be served by the + local server. +

+

+ The name of the local server is important, so we provide it as a + template from which you can create a local server for your + produce. To make this clear, let's assume that your company AlphaTech + produces a telescope system which contains a microcontroller that is + able to control not only the telescope mount, but also a focuser and + a camera rotator. The mount, focuser, and rotator are all controlled + via commands sent through a common serial line connecting the + computer to the microcontroller, so you need a local server. In + ASCOM, then, you probably want your system to appear as + AlphaTech.Telescope, AlphaTech.Focuser, and AlphaTech.Rotator. Then + you would name the local server AlphaTech. Be sure to give this due + consideration before creating the template, the project name is the + name of your local server. + Is this still correct? I get the + impression that ASCOM.AlphaTech.Server would be OK. + +

+

+ The fact that driver classes inherit from the local server's + ReferenceCountedObjectBase class allows the local server to maintain + a reference count on the driver class. If a client creates an + instance of a served driver, the local server automatically starts up + and provides an instance of the class to the client. Once started the + local server can provide additional instances of any of its served + driver classes. If the reference count of all served classes drops to + zero as a result of clients releasing their instances, the local + server will automatically exit. +

+

+ Registration services provided include not only the basic COM + class registration, but also DCOM/AppID info needed to use the served + classes from outbound connections from Software Bisque's TheSky. It + also registers the served classes for the ASCOM Chooser. The + "friendly" name of each served driver that appears in the + chooser comes from the driver's ServedClassName attribute. This also + used to identify a driver so that non driver dlls, such as Interop + dlls can be ignored. The COM ProgID for each served driver is + specified in the ProgId attribute - ASCOM.localservername.drivertype, + for example, ASCOM.AlphaTech.Telescope, where AlphaTech is the local + server name and Telescope is the type of the driver. Unregistering + removes all of this information from the system. Specifying the + ProgId as an attribute allows multiple driver assemblies to be + generated using the same source and namespace. This is used to + provide multiple instances of the same driver, each with a different + ProgId and so able to be registered separately. +

+

+ Driver DLLs are identified for registering/unregistering because + they contain a type with the ServedClassName attribute. Only these + will be registered for Com and ASCOM. This has changed; in Platform + 5 there was no attribute and the local server attempted to register + all dlls. The new behaviour allows support dlls such as interop dlls + to be included without them being registered incorrectly. There was + also an interim version where the ServedClassName attribute was on + the assembly, not the class. + All these previous versions, and the + new drivers will operate together with Platform 6, the changes are + local to the individual drivers. + +

+

+ Detailed Use and Deployment +

+

+ Once you have built your local server and the served driver class + assemblies, here's how to use it. To register the served classes, + activate the local server from a shell command line with the option + /register (or /regserver, for VB6 compatibility): +

+
+      C:\xxx> localserver.exe /register
+    
+

+ To unregister the local server and its drivers, activate the local + server from a shell command line with the option /unregister (or + /unregserver for VB6 compatibility): +

+
+      C:\xxx> localserver.exe /unregister
+    
+

+ When the operating system starts the local server in response to a + client creating one of it's served driver classes, the command option + /embedding is included. The local server's code detects this and sets + a variable that you can use. +

+

+ When deploying a hub or set of drivers + with the local server, you'll have to arrange for the local server + and the driver assemblies to be placed together in a folder in the + ASCOM driver folder. Any support files, such as Interop DLLs can be + put in the same fiolder. That's all you need to do, the local server + will find them in the same folder as it is located in. +

+
+ + + + + + + +
+ + + + + +
+

ASCOM Initiative

+
+ +
+

+
+
+ +

+
+

+ The ASCOM Initiative consists of a group of astronomy software + developers and instrument vendors whose goals are to promote the + driver/client model and scripting automation. +

+

+ See the + ASCOM + web site + for more information. Please participate in the + + ASCOM-Talk + Yahoo Group + . +

+
+
+

+
+
+ +

+

+
+
+ +

+ + \ No newline at end of file diff --git a/Meade.net/ReferenceCountedObject.cs b/Meade.net/ReferenceCountedObject.cs new file mode 100644 index 0000000..7c7b7f2 --- /dev/null +++ b/Meade.net/ReferenceCountedObject.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.InteropServices; + +namespace ASCOM.Meade.net +{ + [ComVisible(false)] + public class ReferenceCountedObjectBase + { + public ReferenceCountedObjectBase() + { + // We increment the global count of objects. + Server.CountObject(); + } + + ~ReferenceCountedObjectBase() + { + // We decrement the global count of objects. + Server.UncountObject(); + // We then immediately test to see if we the conditions + // are right to attempt to terminate this server application. + Server.ExitIf(); + } + } +} diff --git a/Meade.net/Resources/ASCOM.bmp b/Meade.net/Resources/ASCOM.bmp new file mode 100644 index 0000000000000000000000000000000000000000..55516c7aaa3591604865d4cd18379f8f650c3104 GIT binary patch literal 3382 zcmeIwA#&YN425B3CLjUbgJ57$uqM1+gO+lZkut%6E<(#F6wORPc)-i=r?4!c_Vwp; zZ3Fx2>gm_x54#?Zw`LE_etx;JyM1xL@%<_@PLnpcGPUluKQDjn-|L&12NJySLSxLr z;9wF7q0kt!G&lm>q)=$g*LR%^B!og^%$Qm^0tune7=F^=2qc6;W6U^h9D#&TXpDvN zo8~x1LMSw5m*!3e5<;OdyXkPr%u*^;D_frLmI06Zw(3st}a59h(3XRzqWFR3F8nf*xCj$wg(3oxIax#z*3XRz|Zzlr@q3r$RV$Z*g8j2NO iuBTlt#|(_)(Q$tsX9dm*oE11La8}@~z*&L+tOB3FDm>c& literal 0 HcmV?d00001 diff --git a/MeadeAutostar497/AscomClasses/SetupDialogForm.cs b/Meade.net/SetupDialogForm.cs similarity index 66% rename from MeadeAutostar497/AscomClasses/SetupDialogForm.cs rename to Meade.net/SetupDialogForm.cs index 5091c9a..79b1816 100644 --- a/MeadeAutostar497/AscomClasses/SetupDialogForm.cs +++ b/Meade.net/SetupDialogForm.cs @@ -6,9 +6,9 @@ using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; using ASCOM.Utilities; -using ASCOM.MeadeAutostar497; +using ASCOM.Meade.net; -namespace ASCOM.MeadeAutostar497 +namespace ASCOM.Meade.net { [ComVisible(false)] // Form not registered for COM! public partial class SetupDialogForm : Form @@ -16,16 +16,6 @@ namespace ASCOM.MeadeAutostar497 public SetupDialogForm() { InitializeComponent(); - // Initialise current values of user settings from the ASCOM Profile - InitUI(); - } - - private void cmdOK_Click(object sender, EventArgs e) // OK button event handler - { - // Place any validation constraint checks here - // Update the state variables with results from the dialogue - Telescope.comPort = (string)comboBoxComPort.SelectedItem; - Telescope.tl.Enabled = chkTrace.Checked; } private void cmdCancel_Click(object sender, EventArgs e) // Cancel button event handler @@ -48,19 +38,30 @@ namespace ASCOM.MeadeAutostar497 { MessageBox.Show(other.Message); } - } + } - private void InitUI() + public void SetProfile(ProfileProperties profileProperties) { - chkTrace.Checked = Telescope.tl.Enabled; + chkTrace.Checked = profileProperties.TraceLogger; // set the list of com ports to those that are currently available comboBoxComPort.Items.Clear(); comboBoxComPort.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames()); // use System.IO because it's static // select the current port if possible - if (comboBoxComPort.Items.Contains(Telescope.comPort)) + if (comboBoxComPort.Items.Contains(profileProperties.ComPort)) { - comboBoxComPort.SelectedItem = Telescope.comPort; + comboBoxComPort.SelectedItem = profileProperties.ComPort; } } + + public ProfileProperties GetProfile() + { + var profileProperties = new ProfileProperties + { + TraceLogger = chkTrace.Checked, + ComPort = comboBoxComPort.SelectedItem.ToString() + }; + + return profileProperties; + } } } \ No newline at end of file diff --git a/MeadeAutostar497/AscomClasses/SetupDialogForm.designer.cs b/Meade.net/SetupDialogForm.designer.cs similarity index 96% rename from MeadeAutostar497/AscomClasses/SetupDialogForm.designer.cs rename to Meade.net/SetupDialogForm.designer.cs index 913fdc4..881dfcd 100644 --- a/MeadeAutostar497/AscomClasses/SetupDialogForm.designer.cs +++ b/Meade.net/SetupDialogForm.designer.cs @@ -1,4 +1,4 @@ -namespace ASCOM.MeadeAutostar497 +namespace ASCOM.Meade.net { partial class SetupDialogForm { @@ -48,7 +48,6 @@ namespace ASCOM.MeadeAutostar497 this.cmdOK.TabIndex = 0; this.cmdOK.Text = "OK"; this.cmdOK.UseVisualStyleBackColor = true; - this.cmdOK.Click += new System.EventHandler(this.cmdOK_Click); // // cmdCancel // @@ -74,7 +73,7 @@ namespace ASCOM.MeadeAutostar497 // this.picASCOM.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.picASCOM.Cursor = System.Windows.Forms.Cursors.Hand; - this.picASCOM.Image = global::ASCOM.MeadeAutostar497.Properties.Resources.ASCOM; + this.picASCOM.Image = global::ASCOM.Meade.net.Properties.Resources.ASCOM; this.picASCOM.Location = new System.Drawing.Point(292, 9); this.picASCOM.Name = "picASCOM"; this.picASCOM.Size = new System.Drawing.Size(48, 56); @@ -129,7 +128,7 @@ namespace ASCOM.MeadeAutostar497 this.Name = "SetupDialogForm"; this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "MeadeAutostar497 Setup"; + this.Text = "Meade.net Setup"; ((System.ComponentModel.ISupportInitialize)(this.picASCOM)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); diff --git a/MeadeAutostar497/AscomClasses/SetupDialogForm.resx b/Meade.net/SetupDialogForm.resx similarity index 100% rename from MeadeAutostar497/AscomClasses/SetupDialogForm.resx rename to Meade.net/SetupDialogForm.resx diff --git a/Meade.net/SharedResources.cs b/Meade.net/SharedResources.cs new file mode 100644 index 0000000..342e56c --- /dev/null +++ b/Meade.net/SharedResources.cs @@ -0,0 +1,382 @@ +// +// ================ +// Shared Resources +// ================ +// +// This class is a container for all shared resources that may be needed +// by the drivers served by the Local Server. +// +// NOTES: +// +// * ALL DECLARATIONS MUST BE STATIC HERE!! INSTANCES OF THIS CLASS MUST NEVER BE CREATED! +// +// Written by: Bob Denny 29-May-2007 +// Modified by Chris Rowland and Peter Simpson to hamdle multiple hardware devices March 2011 +// +using System; +using System.Collections.Generic; +using System.Text; +using ASCOM; +using ASCOM.Utilities; + +namespace ASCOM.Meade.net +{ + public class ProfileProperties + { + // properies that are part of the profile + public string ComPort { get; set; } + public bool TraceLogger { get; set; } + } + + /// + /// The resources shared by all drivers and devices, in this example it's a serial port with a shared SendMessage method + /// an idea for locking the message and handling connecting is given. + /// In reality extensive changes will probably be needed. + /// Multiple drivers means that several applications connect to the same hardware device, aka a hub. + /// Multiple devices means that there are more than one instance of the hardware, such as two focusers. + /// In this case there needs to be multiple instances of the hardware connector, each with it's own connection count. + /// + public static class SharedResources + { + // object used for locking to prevent multiple drivers accessing common code at the same time + private static readonly object lockObject = new object(); + + // Shared serial port. This will allow multiple drivers to use one single serial port. + private static ASCOM.Utilities.Serial s_sharedSerial = new ASCOM.Utilities.Serial(); // Shared serial port + private static int s_z = 0; // counter for the number of connections to the serial port + + // + // Public access to shared resources + // + + #region single serial port connector + + // + // this region shows a way that a single serial port could be connected to by multiple + // drivers. + // + // Connected is used to handle the connections to the port. + // + // SendMessage is a way that messages could be sent to the hardware without + // conflicts between different drivers. + // + // All this is for a single connection, multiple connections would need multiple ports + // and a way to handle connecting and disconnection from them - see the + // multi driver handling section for ideas. + // + + /// + /// Shared serial port + /// + public static ASCOM.Utilities.Serial SharedSerial + { + get { return s_sharedSerial; } + } + + /// + /// number of connections to the shared serial port + /// + public static int connections + { + get { return s_z; } + set { s_z = value; } + } + + public static void SendBlind(string message) + { + lock (lockObject) + { + SharedSerial.ClearBuffers(); + SharedSerial.Transmit(message); + } + } + + public static bool SendBool(string message) + { + SharedSerial.ClearBuffers(); + return SendChar(message) == "1"; + } + + /// + /// Example of a shared SendMessage method, the lock + /// prevents different drivers tripping over one another. + /// It needs error handling and assumes that the message will be sent unchanged + /// and that the reply will always be terminated by a "#" character. + /// + /// + /// + public static string SendString(string message) + { + lock (lockObject) + { + SharedSerial.ClearBuffers(); + SharedSerial.Transmit(message); + return SharedSerial.ReceiveTerminated("#").TrimEnd('#'); + } + } + + public static string SendChar(string message) + { + lock (lockObject) + { + SharedSerial.ClearBuffers(); + SharedSerial.Transmit(message); + return SharedSerial.ReceiveCounted(1); + } + } + + public static string ReadTerminated() + { + lock (lockObject) + { + return SharedSerial.ReceiveTerminated("#"); + } + } + + /// + /// Example of handling connecting to and disconnection from the + /// shared serial port. + /// Needs error handling + /// the port name etc. needs to be set up first, this could be done by the driver + /// checking Connected and if it's false setting up the port before setting connected to true. + /// It could also be put here. + /// + public static bool Connected + { + set + { + lock (lockObject) + { + if (value) + { + if (s_z == 0) + SharedSerial.Connected = true; + s_z++; + } + else + { + s_z--; + if (s_z <= 0) + { + SharedSerial.Connected = false; + } + } + } + } + get { return SharedSerial.Connected; } + } + + #endregion + + #region Profile + + internal static string driverID = "ASCOM.MeadeGeneric.Telescope"; + + // Constants used for Profile persistence + internal static string comPortProfileName = "COM Port"; + internal static string traceStateProfileName = "Trace Level"; + + public static void WriteProfile(ProfileProperties profileProperties) + { + using (Profile driverProfile = new Profile()) + { + driverProfile.DeviceType = "Telescope"; + driverProfile.WriteValue(driverID, traceStateProfileName, profileProperties.TraceLogger.ToString()); + driverProfile.WriteValue(driverID, comPortProfileName, profileProperties.ComPort); + } + } + + private static readonly string comPortDefault = "COM1"; + internal static string traceStateDefault = "false"; + + public static ProfileProperties ReadProfile() + { + ProfileProperties profileProperties = new ProfileProperties(); + using (Profile driverProfile = new Profile()) + { + driverProfile.DeviceType = "Telescope"; + profileProperties.ComPort = + driverProfile.GetValue(driverID, comPortProfileName, string.Empty, comPortDefault); + profileProperties.TraceLogger = Convert.ToBoolean(driverProfile.GetValue(driverID, + traceStateProfileName, string.Empty, traceStateDefault)); + } + + return profileProperties; + } + + #endregion + + #region SetupDialog + + public static void SetupDialog() + { + // consider only showing the setup dialog if not connected + // or call a different dialog if connected + if (SharedSerial.Connected) + { + System.Windows.Forms.MessageBox.Show("Already connected, please disconnect before altering settings"); + return; + } + + var profileProperties = ReadProfile(); + + using (SetupDialogForm F = new SetupDialogForm()) + { + F.SetProfile(profileProperties); + + var result = F.ShowDialog(); + if (result == System.Windows.Forms.DialogResult.OK) + { + profileProperties = F.GetProfile(); + + WriteProfile(profileProperties); // Persist device configuration values to the ASCOM Profile store + } + } + } + + #endregion + + #region Multi Driver handling + + // this section illustrates how multiple drivers could be handled, + // it's for drivers where multiple connections to the hardware can be made and ensures that the + // hardware is only disconnected from when all the connected devices have disconnected. + + // It is NOT a complete solution! This is to give ideas of what can - or should be done. + // + // An alternative would be to move the hardware control here, handle connecting and disconnecting, + // and provide the device with a suitable connection to the hardware. + // + /// + /// dictionary carrying device connections. + /// The Key is the connection number that identifies the device, it could be the COM port name, + /// USB ID or IP Address, the Value is the DeviceHardware class + /// + private static Dictionary connectedDevices = new Dictionary(); + + /// + /// This is called in the driver Connect(true) property, + /// it add the device id to the list of devices if it's not there and increments the device count. + /// + /// + public static void Connect(string deviceId) + { + lock (lockObject) + { + if (!connectedDevices.ContainsKey(deviceId)) + connectedDevices.Add(deviceId, new DeviceHardware()); + connectedDevices[deviceId].count++; // increment the value + + if (deviceId == "Serial") + { + if (connectedDevices[deviceId].count == 1) + { + var profileProperties = ReadProfile(); + SharedResources.SharedSerial.PortName = profileProperties.ComPort; + SharedResources.SharedSerial.DTREnable = false; + SharedResources.SharedSerial.RTSEnable = false; + SharedResources.SharedSerial.DataBits = 8; + SharedResources.SharedSerial.StopBits = SerialStopBits.One; + SharedResources.SharedSerial.Parity = SerialParity.None; + SharedResources.SharedSerial.Speed = SerialSpeed.ps9600; + SharedResources.SharedSerial.Handshake = SerialHandshake.None; + SharedResources.SharedSerial.Connected = true; + + string firmware = SendString(":GVN#"); + } + } + } + } + + public static void Disconnect(string deviceId) + { + lock (lockObject) + { + if (connectedDevices.ContainsKey(deviceId)) + { + connectedDevices[deviceId].count--; + if (connectedDevices[deviceId].count <= 0) + { + connectedDevices.Remove(deviceId); + if (deviceId == "Serial") + { + SharedResources.SharedSerial.Connected = false; + } + } + } + } + } + + public static bool IsConnected(string deviceId) + { + if (connectedDevices.ContainsKey(deviceId)) + return (connectedDevices[deviceId].count > 0); + else + return false; + } + + #endregion + + public static void Lock(Action action) + { + lock (lockObject) + { + action(); + } + } + + public static T Lock(Func func) + { + lock (lockObject) + { + return func(); + } + } + + /// + /// Skeleton of a hardware class, all this does is hold a count of the connections, + /// in reality extra code will be needed to handle the hardware in some way + /// + public class DeviceHardware + { + private int _count; + + internal int count + { + set => _count = value; + get => _count; + } + + internal DeviceHardware() + { + count = 0; + } + } + + //#region ServedClassName attribute + ///// + ///// This is only needed if the driver is targeted at platform 5.5, it is included with Platform 6 + ///// + //[global::System.AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + //public sealed class ServedClassNameAttribute : Attribute + //{ + // // See the attribute guidelines at + // // http://go.microsoft.com/fwlink/?LinkId=85236 + + // /// + // /// Gets or sets the 'friendly name' of the served class, as registered with the ASCOM Chooser. + // /// + // /// The 'friendly name' of the served class. + // public string DisplayName { get; private set; } + // /// + // /// Initializes a new instance of the class. + // /// + // /// The 'friendly name' of the served class. + // public ServedClassNameAttribute(string servedClassName) + // { + // DisplayName = servedClassName; + // } + //} + //#endregion + } +} \ No newline at end of file diff --git a/Meade.net/app.config b/Meade.net/app.config new file mode 100644 index 0000000..70dcdba --- /dev/null +++ b/Meade.net/app.config @@ -0,0 +1,3 @@ + + + diff --git a/Meade.net/frmMain.Designer.cs b/Meade.net/frmMain.Designer.cs new file mode 100644 index 0000000..83c8dd1 --- /dev/null +++ b/Meade.net/frmMain.Designer.cs @@ -0,0 +1,63 @@ +using System; + +namespace ASCOM.Meade.net +{ + partial class frmMain + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // label1 + // + this.label1.Location = new System.Drawing.Point(12, 10); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(199, 33); + this.label1.TabIndex = 0; + this.label1.Text = "This is an ASCOM driver, not a program for you to use."; + // + // frmMain + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(233, 52); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.Name = "frmMain"; + this.Text = "Meade.net Driver Server"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Label label1; + + } +} + diff --git a/Meade.net/frmMain.cs b/Meade.net/frmMain.cs new file mode 100644 index 0000000..92b3be7 --- /dev/null +++ b/Meade.net/frmMain.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace ASCOM.Meade.net +{ + public partial class frmMain : Form + { + delegate void SetTextCallback(string text); + + public frmMain() + { + InitializeComponent(); + } + + } +} \ No newline at end of file diff --git a/Meade.net/frmMain.resx b/Meade.net/frmMain.resx new file mode 100644 index 0000000..19dc0dd --- /dev/null +++ b/Meade.net/frmMain.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/MeadeAutostar497.UnitTests/BootstrapAscomProfileStore.ps1 b/MeadeAutostar497.UnitTests/BootstrapAscomProfileStore.ps1 deleted file mode 100644 index 213c64d..0000000 --- a/MeadeAutostar497.UnitTests/BootstrapAscomProfileStore.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -<# -This script initializes a bare minimum set of registry entries required for ASCOM.Utilities.Profile to work -without throwing any exceptions. When building on a build server, or on a computer without the ASCOM Platform installed, -it may be useful to execute this script as a build step prior to running any unit tests, or calling any code that relies on -ASCOM.Utilities.Profile. The alternative is to install the ASCOM Platform on the build agent. - -NOTE: This script equires elevated permissions because it creates registry keys in the LocalMachine hive. -#> - -$wow = Test-Path HKLM:\SOFTWARE\Wow6432Node -if ($wow) - { - $root = "HKLM:\SOFTWARE\Wow6432Node" - } -else - { - $root = "HKLM:\SOFTWARE" - } -$ascomRoot = $root + "\ASCOM" - -if (Test-Path $ascomRoot) - { - <# Don't upset an already-existing ASCOM registry #> - exit - } - -<# Create the ASCOM root key and set it's ACL to allow all users read/write access #> -New-Item -Path $root -Name ASCOM –Force -$ascomAcl = Get-Acl $ascomRoot -$aclRule = New-Object System.Security.AccessControl.RegistryAccessRule ("Users","FullControl","Allow") -$ascomAcl.SetAccessRule($aclRule) -$ascomAcl | Set-Acl -Path $ascomRoot - -<# Now create the bare minimum keys required so that ASCOM.Utilities.Profile doesn't crash and burn #> -New-ItemProperty -Path $ascomRoot -Name PlatformVersion -Value "6.1" -PropertyType String –Force -New-ItemProperty -Path $ascomRoot -Name SerTraceFile -Value "C:\SerialTraceAuto.txt" -PropertyType String –Force diff --git a/MeadeAutostar497.UnitTests/MeadeAutostar497.UnitTests.csproj b/MeadeAutostar497.UnitTests/MeadeAutostar497.UnitTests.csproj deleted file mode 100644 index 47bf469..0000000 --- a/MeadeAutostar497.UnitTests/MeadeAutostar497.UnitTests.csproj +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - Debug - AnyCPU - {9638DA27-77C7-4B30-A730-6E7159A4A09F} - Library - Properties - MeadeAutostar497.UnitTests - MeadeAutostar497.UnitTests - v4.6.2 - 512 - true - - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - AnyCPU - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Astrometry.dll - - - ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Attributes.dll - - - ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Cache.dll - - - ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Controls.dll - - - ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.DeviceInterfaces.dll - - - ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.DriverAccess.dll - - - ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Exceptions.dll - - - ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Internal.Extensions.dll - - - ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.SettingsProvider.dll - - - ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Utilities.dll - - - ..\packages\ASCOM.Platform.6.4.2\lib\net40\ASCOM.Utilities.Video.dll - - - ..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll - - - ..\packages\Moq.4.10.1\lib\net45\Moq.dll - - - ..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll - - - - - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll - - - - - ..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll - - - - - - - - - - - - - - - - - - {64308775-bd4a-469c-bcab-3ed830b811af} - MeadeAutostar497 - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - \ No newline at end of file diff --git a/MeadeAutostar497.UnitTests/TelescopeControllerUnitTests.cs b/MeadeAutostar497.UnitTests/TelescopeControllerUnitTests.cs deleted file mode 100644 index 97f4fcf..0000000 --- a/MeadeAutostar497.UnitTests/TelescopeControllerUnitTests.cs +++ /dev/null @@ -1,556 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.IO.Ports; -using ASCOM; -using ASCOM.DeviceInterface; -using ASCOM.MeadeAutostar497.Controller; -using Moq; -using NUnit.Framework; - -namespace MeadeAutostar497.UnitTests -{ - [TestFixture] - public class TelescopeControllerUnitTests - { - private Mock serialMock; - - private readonly List _availableComPorts = new List { "COM1", "COM2", "COM3" }; - private TelescopeController _telescopeController; - - private string _stringToRecieve = string.Empty; - private bool _isConnected = false; - - [SetUp] - public void Setup() - { - _stringToRecieve = string.Empty; - _isConnected = false; - - serialMock = new Mock(); - serialMock.SetupAllProperties(); - serialMock.Setup(x => x.GetPortNames()).Returns(() => _availableComPorts.ToArray()); - serialMock.Setup(x => x.CommandTerminated(It.IsAny(), It.IsAny())) - .Returns(() => _stringToRecieve); - serialMock.Setup(x => x.IsOpen).Returns(() => _isConnected); - - //Todo inject the serialMock instead of using a singleton to increase code stability. - _telescopeController = TelescopeController.Instance; - _telescopeController.SerialPort = serialMock.Object; - } - - [TearDown] - public void TearDown() - { - _isConnected = false; - _telescopeController.Connected = false; - _telescopeController.Port = "COM1"; - } - - [Test] - public void ImplementsExpectedInterfaces() - { - Assert.That(_telescopeController, Is.Not.Null); - Assert.That(_telescopeController, Is.AssignableTo()); - } - - [Test] - public void NotConnectedByDefault() - { - Assert.That(_telescopeController.Connected, Is.False); - } - - [Test] - public void ConnectedCanBeSetTrue() - { - _stringToRecieve = "test#"; - _isConnected = true; - - _telescopeController.Connected = true; - Assert.That(_telescopeController.Connected, Is.True); - } - - [Test] - public void EnsureThatTheSerialCommunicationsAreSetCorrectly() - { - Assert.That(serialMock.Object.IsOpen, Is.False); - - _stringToRecieve = "test#"; - _telescopeController.Connected = true; - _isConnected = true; - Assert.That(_telescopeController.Connected, Is.True); - - serialMock.Verify(x => x.Open(), Times.Once); - - Assert.That(serialMock.Object.DtrEnable, Is.False); - Assert.That(serialMock.Object.RtsEnable, Is.False); - Assert.That(serialMock.Object.BaudRate, Is.EqualTo(9600)); - Assert.That(serialMock.Object.DataBits, Is.EqualTo(8)); - Assert.That(serialMock.Object.StopBits, Is.EqualTo(StopBits.One)); - Assert.That(serialMock.Object.Parity, Is.EqualTo(Parity.None)); - Assert.That(serialMock.Object.PortName, Is.EqualTo(_telescopeController.Port)); - Assert.That(serialMock.Object.IsOpen, Is.True); - - } - - [Test] - public void WhenOpensComPortToNonAutostarThrowException() - { - Assert.That(serialMock.Object.IsOpen, Is.False); - var exception = Assert.Throws(() => { _telescopeController.Connected = true; }); - - Assert.That(exception.Message, Is.EqualTo("Failed to communicate with telescope.")); - - Assert.That(_telescopeController.Connected, Is.False); - } - - [Test] - public void CannotChangeSerialPortObjectWhenConnected() - { - _stringToRecieve = "test#"; - _isConnected = true; - - _telescopeController.Connected = true; - - Mock newSerialMock = new Mock(); - - var exception = Assert.Throws( () => { _telescopeController.SerialPort = newSerialMock.Object; }); - - Assert.That(exception, Is.Not.Null); - Assert.That(exception.Message, Is.EqualTo("Please disconnect before changing the serial engine.")); - } - - [Test] - public void PortIsSetToCom1ByDefault() - { - Assert.That(_telescopeController.Port, Is.EqualTo("COM1")); - } - - [Test] - public void SettingPortToValidPortAllowed() - { - _telescopeController.Port = "COM2"; - - Assert.That(_telescopeController.Port, Is.EqualTo("COM2")); - } - - [Test] - public void SettingPortToValidPortWhenConnectedFails() - { - _stringToRecieve = "test#"; - _isConnected = true; - - _telescopeController.Connected = true; - var exception = Assert.Throws( () => _telescopeController.Port = "COM2"); - - Assert.That(exception.Message, Is.EqualTo("Please disconnect from the scope before changing port.")); - - Assert.That(_telescopeController.Port, Is.EqualTo("COM1")); //port hasn't changed - } - - [Test] - public void SettingPortToInvalidPortFails() - { - var exception = Assert.Throws(() => _telescopeController.Port = "COM5"); - - Assert.That(exception.Message, Is.EqualTo("Unable to select port COM5 as it does not exist.")); - - Assert.That(_telescopeController.Port, Is.EqualTo("COM1")); //port hasn't changed - } - - [Test] - public void AbortSlewWorks() - { - _isConnected = true; - - _telescopeController.Connected = true; - - _telescopeController.AbortSlew(); - - serialMock.Verify(x => x.Command("#:Q#"), Times.Once); - } - - [Test] - public void SlewingReturnTrueAsExpected() - { - _isConnected = true; - - _telescopeController.Connected = true; - - serialMock.Setup(x => x.CommandTerminated(":D#", "#")).Returns("|"); - - var slewing = _telescopeController.Slewing; - - Assert.That(slewing, Is.True); - } - - [Test] - public void SlewingReturnFalseAsExpected() - { - _isConnected = true; - - _telescopeController.Connected = true; - - serialMock.Setup(x => x.CommandTerminated(":D#", "#")).Returns(string.Empty); - - var slewing = _telescopeController.Slewing; - - Assert.That(slewing, Is.False); - } - - [Test] - public void utcDate_Get_ReturnsExpectedValue() - { - DateTime expectedDate = new DateTime(2019, 04, 30, 11, 32, 24, DateTimeKind.Local); - - var dateString = "04/30/19"; - var timeString = "12:32:24"; - - _isConnected = true; - - _telescopeController.Connected = true; - - serialMock.Setup(x => x.CommandTerminated(":GG#", "#")).Returns("-01"); - serialMock.Setup(x => x.CommandTerminated(":GC#", "#")).Returns(dateString); - serialMock.Setup(x => x.CommandTerminated(":GL#", "#")).Returns(timeString); - - var result = _telescopeController.utcDate; - - Assert.That(result, Is.EqualTo(expectedDate)); - } - - [Test] - public void utcDate_Set_SetsTelescopeDateAndTime() - { - DateTime testDateTime = new DateTime(2019, 04, 30, 19, 53, 32, DateTimeKind.Utc); - - serialMock.Setup(x => x.CommandTerminated(":GG#", "#")).Returns("-01"); - serialMock.Setup(x => x.CommandChar($":SL{testDateTime.Hour+1:00}:{testDateTime.Minute:00}:{testDateTime.Second:00}#")).Returns('1'); - serialMock.Setup(x => x.CommandChar($":SC{testDateTime.Month:00}/{testDateTime.Day:00}/{testDateTime:yy}#")).Returns('1'); - - _isConnected = true; - - _telescopeController.Connected = true; - - _telescopeController.utcDate = testDateTime; - } - - [Test] - public void utcDate_Set_ThrowsExceptionWhenTimeInvalid() - { - DateTime testDateTime = new DateTime(2019, 04, 30, 19, 53, 32, DateTimeKind.Utc); - - serialMock.Setup(x => x.CommandTerminated(":GG#", "#")).Returns("-01"); - //serialMock.Setup(x => x.CommandChar($":SL{testDateTime.Hour:00}:{testDateTime.Minute:00}:{testDateTime.Second:00}#")).Returns('1'); - serialMock.Setup(x => x.CommandChar($":SC{testDateTime.Month:00}/{testDateTime.Day:00}/{testDateTime:yy}#")).Returns('1'); - - _isConnected = true; - - _telescopeController.Connected = true; - - var exception = Assert.Throws( () => {_telescopeController.utcDate = testDateTime; }); - - Assert.That( exception.Message, Is.EqualTo("Failed to set local time")); - } - - [Test] - public void utcDate_Set_ThrowsExceptionWhenDateInvalid() - { - DateTime testDateTime = new DateTime(2019, 04, 30, 19, 53, 32, DateTimeKind.Local); - - serialMock.Setup(x => x.CommandTerminated(":GG#", "#")).Returns("-01"); - serialMock.Setup(x => x.CommandChar($":SL{testDateTime.Hour+1:00}:{testDateTime.Minute:00}:{testDateTime.Second:00}#")).Returns('1'); - //serialMock.Setup(x => x.CommandChar($":SC{testDateTime.Month:00}/{testDateTime.Day:00}/{testDateTime:yy}#")).Returns('1'); - - _isConnected = true; - - _telescopeController.Connected = true; - - var exception = Assert.Throws(() => { _telescopeController.utcDate = testDateTime; }); - - Assert.That(exception.Message, Is.EqualTo("Failed to set local date")); - } - - [TestCase("+12*34", 12.566666666666666)] - [TestCase("+12*34.56", 12.582222222222223)] - [TestCase("-67*34.56", -67.582222222222214)] - public void SiteLatitude_Get_ReturnsExpectedDouble( string latitude, double expectedResult) - { - serialMock.Setup(x => x.CommandTerminated(":Gt#", "#")).Returns(latitude); - - _isConnected = true; - - _telescopeController.Connected = true; - - var result = _telescopeController.SiteLatitude; - - Assert.That(result, Is.EqualTo(expectedResult)); - } - - [Test] - public void SiteLatitude_Set_ThrowsExeptionWhenValueTooSmall() - { - _isConnected = true; - - _telescopeController.Connected = true; - - var exception = Assert.Throws( () => { _telescopeController.SiteLatitude = -91;}); - - Assert.That(exception.Message, Is.EqualTo("Latitude cannot be less than -90 degrees.")); - } - - [Test] - public void SiteLatitude_Set_ThrowsExeptionWhenValueTooLarge() - { - _isConnected = true; - - _telescopeController.Connected = true; - - var exception = Assert.Throws(() => { _telescopeController.SiteLatitude = 91; }); - - Assert.That(exception.Message, Is.EqualTo("Latitude cannot be greater than 90 degrees.")); - } - - [Test] - public void SiteLatitude_Set_ThrowsExeptionWhenTelescopeReportsFail() - { - _isConnected = true; - - _telescopeController.Connected = true; - - var exception = Assert.Throws(() => { _telescopeController.SiteLatitude = 10; }); - - Assert.That(exception.Message, Is.EqualTo("Failed to set site latitude.")); - } - - [Test] - public void SiteLatitude_Set_NoErrorWhenValidValueSentSuccessfully() - { - serialMock.Setup(x => x.CommandChar(":Sts10*00#")).Returns('1'); - - _isConnected = true; - - _telescopeController.Connected = true; - - _telescopeController.SiteLatitude = 10; - } - - - [TestCase("012*34", 12.566666666666666)] - [TestCase("012:34.56", 12.582222222222223)] - [TestCase("350:34.56", -9.4177777777777578)] - public void SiteLongitude_Get_ReturnsExpectedDouble(string longitude, double expectedResult) - { - serialMock.Setup(x => x.CommandTerminated(":Gg#", "#")).Returns(longitude); - - _isConnected = true; - - _telescopeController.Connected = true; - - var result = _telescopeController.SiteLongitude; - - Assert.That(result, Is.EqualTo(expectedResult)); - } - - [Test] - public void SiteLongitude_Set_ThrowsExeptionWhenValueTooSmall() - { - _isConnected = true; - - _telescopeController.Connected = true; - - var exception = Assert.Throws(() => { _telescopeController.SiteLongitude = -181; }); - - Assert.That(exception.Message, Is.EqualTo("Longitude cannot be lower than -180 degrees.")); - } - - [Test] - public void SiteLongitude_Set_ThrowsExeptionWhenValueTooLarge() - { - _isConnected = true; - - _telescopeController.Connected = true; - - var exception = Assert.Throws(() => { _telescopeController.SiteLongitude = 181; }); - - Assert.That(exception.Message, Is.EqualTo("Longitude cannot be greater than 180 degrees.")); - } - - [Test] - public void SiteLongitude_Set_ThrowsExeptionWhenTelescopeReportsFail() - { - _isConnected = true; - - _telescopeController.Connected = true; - - var exception = Assert.Throws(() => { _telescopeController.SiteLongitude = 10; }); - - Assert.That(exception.Message, Is.EqualTo("Failed to set site longitude.")); - } - - [Test] - public void SiteLongitude_Set_NoErrorWhenValidValueSentSuccessfully() - { - serialMock.Setup(x => x.CommandChar(":Sg010*00#")).Returns('1'); - - _isConnected = true; - - _telescopeController.Connected = true; - - _telescopeController.SiteLongitude = 10; - } - - [Test] - public void PulseGuideEast() - { - _isConnected = true; - - _telescopeController.Connected = true; - - _telescopeController.PulseGuide(GuideDirections.guideEast,100); - - serialMock.Verify( x => x.Command(":Mge0100#"), Times.Once); - } - - [Test] - public void PulseGuideWest() - { - _isConnected = true; - - _telescopeController.Connected = true; - - _telescopeController.PulseGuide(GuideDirections.guideWest, 1200); - - serialMock.Verify(x => x.Command(":Mgw1200#"), Times.Once); - } - - [Test] - public void PulseGuideNorth() - { - _isConnected = true; - - _telescopeController.Connected = true; - - _telescopeController.PulseGuide(GuideDirections.guideNorth, 256); - - serialMock.Verify(x => x.Command(":Mgn0256#"), Times.Once); - } - - [Test] - public void PulseGuideSouth() - { - _isConnected = true; - - _telescopeController.Connected = true; - - _telescopeController.PulseGuide(GuideDirections.guideSouth, 1024); - - serialMock.Verify(x => x.Command(":Mgs1024#"), Times.Once); - } - - [TestCase('A', AlignmentModes.algAltAz)] - [TestCase('P', AlignmentModes.algPolar)] - public void AlignmentMode_Get_ReturnsExpectedValue(char commandResponse, AlignmentModes mode) - { - const char ack = (char)6; - - serialMock.Setup(x => x.CommandChar(ack.ToString())).Returns(commandResponse); - - _isConnected = true; - - _telescopeController.Connected = true; - - var result = _telescopeController.AlignmentMode; - - Assert.That(result, Is.EqualTo(mode)); - } - - [TestCase(AlignmentModes.algAltAz, ":AA#")] - [TestCase(AlignmentModes.algPolar, ":AP#")] - [TestCase(AlignmentModes.algGermanPolar, ":AP#")] - public void AligmentMode_Set_WorksAsExpected(AlignmentModes mode, string command) - { - _isConnected = true; - - _telescopeController.Connected = true; - - _telescopeController.AlignmentMode = mode; - - serialMock.Verify( x => x.Command(command), Times.Once); - } - - [Test] - public void AtParkIsFalseByDefault() - { - _isConnected = true; - _telescopeController.Connected = true; - - Assert.That( _telescopeController.AtPark, Is.False ); - } - - [Test] - public void AtParkIsTrueAfterParkingScope() - { - _isConnected = true; - - _telescopeController.Connected = true; - _telescopeController.Park(); - - Assert.That(_telescopeController.AtPark, Is.True); - } - - [Test] - public void Park_CallingParkSendsTheParkCommand() - { - _isConnected = true; - - _telescopeController.Connected = true; - _telescopeController.Park(); - - serialMock.Verify( x => x.Command(":hP#"), Times.Once); - } - - [Test] - public void Park_ParkingSecondTimeDoesNothing() - { - _isConnected = true; - - _telescopeController.Connected = true; - _telescopeController.Park(); - - _telescopeController.Park(); - - serialMock.Verify(x => x.Command(":hP#"), Times.Once); - } - - [TestCase("356*13",356.21666666666664)] - [TestCase("356*13'21", 356.22249999999997)] - public void Azimuth_CanGetValue( string response, double expectedResult ) - { - serialMock.Setup(x => x.CommandTerminated(":GZ#", "#")).Returns(response); - - _isConnected = true; - - _telescopeController.Connected = true; - - var az = _telescopeController.Azimuth; - - Assert.That( az, Is.EqualTo(expectedResult)); - } - - [TestCase("+75*13", 75.2166666666666654)] - [TestCase("+65*13'21", 65.222499999999997)] - public void Declination_CanGetValue(string response, double expectedResult) - { - serialMock.Setup(x => x.CommandTerminated(":GD#", "#")).Returns(response); - - _isConnected = true; - - _telescopeController.Connected = true; - - var result = _telescopeController.Declination; - - Assert.That(result, Is.EqualTo(expectedResult)); - } - } -} diff --git a/MeadeAutostar497.UnitTests/packages.config b/MeadeAutostar497.UnitTests/packages.config deleted file mode 100644 index e7172db..0000000 --- a/MeadeAutostar497.UnitTests/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/MeadeAutostar497.sln b/MeadeAutostar497.sln deleted file mode 100644 index 69570e9..0000000 --- a/MeadeAutostar497.sln +++ /dev/null @@ -1,49 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.136 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeadeAutostar497", "MeadeAutostar497\MeadeAutostar497.csproj", "{64308775-BD4A-469C-BCAB-3ED830B811AF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeadeAutostar497.UnitTests", "MeadeAutostar497.UnitTests\MeadeAutostar497.UnitTests.csproj", "{9638DA27-77C7-4B30-A730-6E7159A4A09F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsole", "TestConsole\TestConsole.csproj", "{D5207217-61C7-4E94-8097-91DBACE57D2A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {64308775-BD4A-469C-BCAB-3ED830B811AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {64308775-BD4A-469C-BCAB-3ED830B811AF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {64308775-BD4A-469C-BCAB-3ED830B811AF}.Debug|x86.ActiveCfg = Debug|Any CPU - {64308775-BD4A-469C-BCAB-3ED830B811AF}.Debug|x86.Build.0 = Debug|Any CPU - {64308775-BD4A-469C-BCAB-3ED830B811AF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {64308775-BD4A-469C-BCAB-3ED830B811AF}.Release|Any CPU.Build.0 = Release|Any CPU - {64308775-BD4A-469C-BCAB-3ED830B811AF}.Release|x86.ActiveCfg = Release|Any CPU - {64308775-BD4A-469C-BCAB-3ED830B811AF}.Release|x86.Build.0 = Release|Any CPU - {9638DA27-77C7-4B30-A730-6E7159A4A09F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9638DA27-77C7-4B30-A730-6E7159A4A09F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9638DA27-77C7-4B30-A730-6E7159A4A09F}.Debug|x86.ActiveCfg = Debug|Any CPU - {9638DA27-77C7-4B30-A730-6E7159A4A09F}.Debug|x86.Build.0 = Debug|Any CPU - {9638DA27-77C7-4B30-A730-6E7159A4A09F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9638DA27-77C7-4B30-A730-6E7159A4A09F}.Release|Any CPU.Build.0 = Release|Any CPU - {9638DA27-77C7-4B30-A730-6E7159A4A09F}.Release|x86.ActiveCfg = Release|Any CPU - {9638DA27-77C7-4B30-A730-6E7159A4A09F}.Release|x86.Build.0 = Release|Any CPU - {D5207217-61C7-4E94-8097-91DBACE57D2A}.Debug|Any CPU.ActiveCfg = Debug|x86 - {D5207217-61C7-4E94-8097-91DBACE57D2A}.Debug|x86.ActiveCfg = Debug|x86 - {D5207217-61C7-4E94-8097-91DBACE57D2A}.Debug|x86.Build.0 = Debug|x86 - {D5207217-61C7-4E94-8097-91DBACE57D2A}.Release|Any CPU.ActiveCfg = Release|x86 - {D5207217-61C7-4E94-8097-91DBACE57D2A}.Release|x86.ActiveCfg = Release|x86 - {D5207217-61C7-4E94-8097-91DBACE57D2A}.Release|x86.Build.0 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {FD610065-5DB2-4D32-8760-4E0552901C28} - EndGlobalSection -EndGlobal diff --git a/MeadeAutostar497/AscomClasses/Telescope.cs b/MeadeAutostar497/AscomClasses/Telescope.cs deleted file mode 100644 index 5f3c794..0000000 --- a/MeadeAutostar497/AscomClasses/Telescope.cs +++ /dev/null @@ -1,1226 +0,0 @@ -//tabs=4 -// -------------------------------------------------------------------------------- -// TODO fill in this information for your driver, then remove this line! -// -// ASCOM Telescope driver for MeadeAutostar497 -// -// Description: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam -// nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam -// erat, sed diam voluptua. At vero eos et accusam et justo duo -// dolores et ea rebum. Stet clita kasd gubergren, no sea takimata -// sanctus est Lorem ipsum dolor sit amet. -// -// Implements: ASCOM Telescope interface version: -// Author: (XXX) Your N. Here -// -// Edit Log: -// -// Date Who Vers Description -// ----------- --- ----- ------------------------------------------------------- -// dd-mmm-yyyy XXX 6.0.0 Initial edit, created from ASCOM driver template -// -------------------------------------------------------------------------------- -// - - -// This is used to define code in the template that is specific to one class implementation -// unused code canbe deleted and this definition removed. -#define Telescope - -using System; -using System.Runtime.InteropServices; -using ASCOM; -using ASCOM.Astrometry; -using ASCOM.Astrometry.AstroUtils; -using ASCOM.Utilities; -using ASCOM.DeviceInterface; -using System.Globalization; -using System.Collections; -using ASCOM.MeadeAutostar497.Controller; -using static System.String; - -namespace ASCOM.MeadeAutostar497 -{ - // - // Your driver's DeviceID is ASCOM.MeadeAutostar497.Telescope - // - // The Guid attribute sets the CLSID for ASCOM.MeadeAutostar497.Telescope - // The ClassInterface/None addribute prevents an empty interface called - // _MeadeAutostar497 from being created and used as the [default] interface - // - // TODO Replace the not implemented exceptions with code to implement the function or - // throw the appropriate ASCOM exception. - // - - /// - /// ASCOM Telescope Driver for MeadeAutostar497. - /// - [Guid("58e4fe97-1760-4e22-8ecd-2225876aeefc")] - [ClassInterface(ClassInterfaceType.None)] - public class Telescope : ITelescopeV3, IFocuserV3 - { - private ITelescopeController _telescopeController; - - /// - /// 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.MeadeAutostar497.Telescope"; - // TODO Change the descriptive string for your driver then remove this line - /// - /// Driver description that displays in the ASCOM Chooser. - /// - private static string driverDescription = "Meade Autostar 497 .net"; - - internal static string comPortProfileName = "COM Port"; // Constants used for Profile persistence - internal static string comPortDefault = "COM1"; - internal static string traceStateProfileName = "Trace Level"; - internal static string traceStateDefault = "false"; - - internal static string comPort; // Variables to hold the currrent device configuration - - /// - /// Private variable to hold an ASCOM Utilities object - /// - private Util utilities; - - /// - /// Private variable to hold an ASCOM AstroUtilities object to provide the Range method - /// - private AstroUtils astroUtilities; - - /// - /// Variable to hold the trace logger object (creates a diagnostic log file with information that you specify) - /// - internal static TraceLogger tl; - - /// - /// Initializes a new instance of the class. - /// Must be public for COM registration. - /// - public Telescope() - { - tl = new TraceLogger("", "MeadeAutostar497"); - ReadProfile(); // Read device configuration from the ASCOM Profile store - - tl.LogMessage("Telescope", "Starting initialisation"); - - utilities = new Util(); //Initialise util object - astroUtilities = new AstroUtils(); // Initialise astro utilities object - - //TODO: Implement your additional construction here - _telescopeController = TelescopeController.Instance; - - tl.LogMessage("Telescope", "Completed initialisation"); - } - - - // - // 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() - { - // 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"); - return; - } - - 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 - { - tl.LogMessage("SupportedActions Get", "Returning empty arraylist"); - return new ArrayList(); - } - } - - public string Action(string actionName, string actionParameters) - { - LogMessage("", "Action {0}, parameters {1} not implemented", actionName, actionParameters); - throw new ASCOM.ActionNotImplementedException("Action " + actionName + " is not implemented by this driver"); - } - - public void CommandBlind(string command, bool raw) - { - tl.LogMessage("CommandBlind", $"command={command} raw={raw}"); - - CheckConnected("CommandBlind"); - // Call CommandString and return as soon as it finishes - //this.CommandString(command, raw); - _telescopeController.CommandBlind(command, raw); - // or - //throw new ASCOM.MethodNotImplementedException("CommandBlind"); - // DO NOT have both these sections! One or the other - } - - public bool CommandBool(string command, bool raw) - { - tl.LogMessage("CommandBool", $"command={command} raw={raw}"); - CheckConnected("CommandBool"); - string ret = CommandString(command, raw); - // TODO decode the return string and return true or false - // or - throw new ASCOM.MethodNotImplementedException("CommandBool"); - // DO NOT have both these sections! One or the other - } - - public string CommandString(string command, bool raw) - { - tl.LogMessage("CommandString", $"command={command} raw={raw}"); - // 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 - CheckConnected("CommandString"); - //throw new ASCOM.MethodNotImplementedException("CommandString"); - return _telescopeController.CommandString(command, raw); - } - - public void Dispose() - { - _telescopeController.Connected = false; - - // Clean up the tracelogger and util objects - tl.Enabled = false; - tl.Dispose(); - tl = null; - utilities.Dispose(); - utilities = null; - astroUtilities.Dispose(); - astroUtilities = null; - } - - public bool Connected - { - get - { - LogMessage("Connected", "Get {0}", IsConnected); - return IsConnected; - } - set - { - tl.LogMessage("Connected", "Set {0}", value); - if (value == IsConnected) - return; - - if (value) - { - LogMessage("Connected Set", "Connecting to port {0}", comPort); - _telescopeController.Port = comPort; - _telescopeController.Connected = true; - } - else - { - LogMessage("Connected Set", "Disconnecting from port {0}", comPort); - _telescopeController.Connected = false; - } - } - } - - public string Description - { - // TODO customise this device description - get - { - tl.LogMessage("Description Get", driverDescription); - return driverDescription; - } - } - - public string DriverInfo - { - get - { - Version version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; - // TODO customise this driver description - string driverInfo = Format(CultureInfo.InvariantCulture, "Information about the driver itself. Version: {0}.{1}", version.Major, version.Minor); - tl.LogMessage("DriverInfo Get", driverInfo); - return driverInfo; - } - } - - public string DriverVersion - { - get - { - Version version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; - string driverVersion = Format(CultureInfo.InvariantCulture, "{0}.{1}", version.Major, version.Minor); - tl.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 = "Meade Autostar 497 .net"; - tl.LogMessage("Name Get", name); - return name; - } - } - - #endregion - - #region ITelescope Implementation - public void AbortSlew() - { - tl.LogMessage("AbortSlew", "Aborting slew"); - _telescopeController.AbortSlew(); - } - - public AlignmentModes AlignmentMode - { - get - { - tl.LogMessage("AlignmentMode Get", "Getting alignmode"); - var alignmode = _telescopeController.AlignmentMode; - tl.LogMessage("AlignmentMode Get", $"alignmode = {alignmode}"); - return alignmode; - } - } - - public double Altitude - { - get - { - var alt = _telescopeController.Altitude; - tl.LogMessage("Altitude", $"{alt}"); - return alt; - } - } - - public double ApertureArea - { - get - { - tl.LogMessage("ApertureArea Get", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("ApertureArea", false); - } - } - - public double ApertureDiameter - { - get - { - tl.LogMessage("ApertureDiameter Get", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("ApertureDiameter", false); - } - } - - public bool AtHome - { - get - { - tl.LogMessage("AtHome", "Get - " + false.ToString()); - return false; - } - } - - public bool AtPark - { - get - { - var atPatk = _telescopeController.AtPark; - tl.LogMessage("AtPark", "Get - " + atPatk.ToString()); - return atPatk; - } - } - - public IAxisRates AxisRates(TelescopeAxes Axis) - { - tl.LogMessage("AxisRates", "Get - " + Axis.ToString()); - return new AxisRates(Axis); - } - - public double Azimuth - { - get - { - var az = _telescopeController.Azimuth; - tl.LogMessage("Azimuth Get", $"{az}"); - return az; - } - } - - public bool CanFindHome - { - get - { - tl.LogMessage("CanFindHome", "Get - " + false.ToString()); - return false; - } - } - - public bool CanMoveAxis(TelescopeAxes Axis) - { - tl.LogMessage("CanMoveAxis", "Get - " + Axis.ToString()); - 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 - { - tl.LogMessage("CanPark", "Get - " + true.ToString()); - return true; - } - } - - public bool CanPulseGuide - { - get - { - tl.LogMessage("CanPulseGuide", "Get - " + true.ToString()); - return true; - } - } - - public bool CanSetDeclinationRate - { - get - { - tl.LogMessage("CanSetDeclinationRate", "Get - " + false.ToString()); - return false; - } - } - - public bool CanSetGuideRates - { - get - { - tl.LogMessage("CanSetGuideRates", "Get - " + false.ToString()); - return false; - } - } - - public bool CanSetPark - { - get - { - tl.LogMessage("CanSetPark", "Get - " + false.ToString()); - return false; - } - } - - public bool CanSetPierSide - { - get - { - tl.LogMessage("CanSetPierSide", "Get - " + false.ToString()); - return false; - } - } - - public bool CanSetRightAscensionRate - { - get - { - tl.LogMessage("CanSetRightAscensionRate", "Get - " + false.ToString()); - return false; - } - } - - public bool CanSetTracking - { - get - { - tl.LogMessage("CanSetTracking", "Get - " + true.ToString()); - return true; - } - } - - public bool CanSlew - { - get - { - tl.LogMessage("CanSlew", "Get - " + true.ToString()); - return true; - } - } - - public bool CanSlewAltAz - { - get - { - tl.LogMessage("CanSlewAltAz", "Get - " + true.ToString()); - return true; - } - } - - public bool CanSlewAltAzAsync - { - get - { - tl.LogMessage("CanSlewAltAzAsync", "Get - " + true.ToString()); - return true; - } - } - - public bool CanSlewAsync - { - get - { - tl.LogMessage("CanSlewAsync", "Get - " + true.ToString()); - return true; - } - } - - public bool CanSync - { - get - { - tl.LogMessage("CanSync", "Get - " + true.ToString()); - return true; - } - } - - public bool CanSyncAltAz - { - get - { - tl.LogMessage("CanSyncAltAz", "Get - " + false.ToString()); - return false; - } - } - - public bool CanUnpark - { - get - { - tl.LogMessage("CanUnpark", "Get - " + false.ToString()); - return false; - } - } - - public double Declination - { - get - { - double declination = _telescopeController.Declination; - tl.LogMessage("Declination", "Get - " + utilities.DegreesToDMS(declination, ":", ":")); - return declination; - } - } - - public double DeclinationRate - { - get - { - double declination = 0.0; - tl.LogMessage("DeclinationRate", "Get - " + declination.ToString()); - return declination; - } - set - { - tl.LogMessage("DeclinationRate Set", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("DeclinationRate", true); - } - } - - public PierSide DestinationSideOfPier(double RightAscension, double Declination) - { - tl.LogMessage("DestinationSideOfPier Get", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("DestinationSideOfPier", false); - } - - public bool DoesRefraction - { - get - { - tl.LogMessage("DoesRefraction Get", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("DoesRefraction", false); - } - set - { - tl.LogMessage("DoesRefraction Set", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("DoesRefraction", true); - } - } - - public EquatorialCoordinateType EquatorialSystem - { - get - { - EquatorialCoordinateType equatorialSystem = EquatorialCoordinateType.equTopocentric; - tl.LogMessage("DeclinationRate", "Get - " + equatorialSystem.ToString()); - return equatorialSystem; - } - } - - public void FindHome() - { - tl.LogMessage("FindHome", "Not implemented"); - throw new ASCOM.MethodNotImplementedException("FindHome"); - } - - public double FocalLength - { - get - { - tl.LogMessage("FocalLength Get", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("FocalLength", false); - } - } - - public double GuideRateDeclination - { - get - { - tl.LogMessage("GuideRateDeclination Get", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("GuideRateDeclination", false); - } - set - { - tl.LogMessage("GuideRateDeclination Set", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("GuideRateDeclination", true); - } - } - - public double GuideRateRightAscension - { - get - { - tl.LogMessage("GuideRateRightAscension Get", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("GuideRateRightAscension", false); - } - set - { - tl.LogMessage("GuideRateRightAscension Set", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("GuideRateRightAscension", true); - } - } - - public bool IsPulseGuiding - { - get - { - tl.LogMessage("IsPulseGuiding Get", "pulse guiding is synchronous for this driver"); - //throw new ASCOM.PropertyNotImplementedException("IsPulseGuiding", false); - return false; - } - } - - public void MoveAxis(TelescopeAxes Axis, double Rate) - { - tl.LogMessage("MoveAxis", $"Axis={Axis} rate={Rate}"); - _telescopeController.MoveAxis(Axis, Rate); - } - - public void Park() - { - tl.LogMessage("Park", "Parking telescope"); - _telescopeController.Park(); - } - - public void PulseGuide(GuideDirections Direction, int Duration) - { - tl.LogMessage("PulseGuide", $"pulse guide direction {Direction} duration {Duration}"); - _telescopeController.PulseGuide(Direction, Duration); - } - - public double RightAscension - { - get - { - double rightAscension = _telescopeController.RightAscension; - tl.LogMessage("RightAscension", "Get - " + utilities.HoursToHMS(rightAscension)); - return rightAscension; - } - } - - public double RightAscensionRate - { - get - { - double rightAscensionRate = 0.0; - tl.LogMessage("RightAscensionRate", "Get - " + rightAscensionRate.ToString()); - return rightAscensionRate; - } - set - { - tl.LogMessage("RightAscensionRate Set", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("RightAscensionRate", true); - } - } - - public void SetPark() - { - tl.LogMessage("SetPark", "Not implemented"); - throw new ASCOM.MethodNotImplementedException("SetPark"); - } - - public PierSide SideOfPier - { - get - { - tl.LogMessage("SideOfPier Get", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("SideOfPier", false); - } - set - { - tl.LogMessage("SideOfPier Set", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("SideOfPier", true); - } - } - - public double SiderealTime - { - get - { - // Now using NOVAS 3.1 - double siderealTime = 0.0; - using (var novas = new ASCOM.Astrometry.NOVAS.NOVAS31()) - { - var jd = utilities.DateUTCToJulian(DateTime.UtcNow); - novas.SiderealTime(jd, 0, novas.DeltaT(jd), - ASCOM.Astrometry.GstType.GreenwichApparentSiderealTime, - ASCOM.Astrometry.Method.EquinoxBased, - ASCOM.Astrometry.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); - - tl.LogMessage("SiderealTime", "Get - " + siderealTime.ToString()); - return siderealTime; - } - } - - public double SiteElevation - { - get - { - tl.LogMessage("SiteElevation Get", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("SiteElevation", false); - } - set - { - tl.LogMessage("SiteElevation Set", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("SiteElevation", true); - } - } - - public double SiteLatitude - { - get - { - var siteLatitude = _telescopeController.SiteLatitude; - tl.LogMessage("SiteLatitude Get", $"{utilities.DegreesToDMS(siteLatitude)}"); - return siteLatitude; - } - set - { - tl.LogMessage("SiteLatitude Set", $"{utilities.DegreesToDMS(value)}"); - _telescopeController.SiteLatitude = value; - } - } - - public double SiteLongitude - { - get - { - var siteLongitude = _telescopeController.SiteLongitude; - tl.LogMessage("SiteLongitude Get", $"{utilities.DegreesToDMS(siteLongitude)}"); - return siteLongitude; - } - set - { - tl.LogMessage("SiteLongitude Set", $"{utilities.DegreesToDMS(value)}"); - _telescopeController.SiteLongitude = value; - } - } - - public short SlewSettleTime - { - get - { - tl.LogMessage("SlewSettleTime Get", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("SlewSettleTime", false); - } - set - { - tl.LogMessage("SlewSettleTime Set", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("SlewSettleTime", true); - } - } - - public void SlewToAltAz(double Azimuth, double Altitude) - { - tl.LogMessage("SlewToAltAz", $"Az=~{Azimuth} Alt={Altitude}"); - _telescopeController.SlewToAltAz(Azimuth, Altitude); - } - - public void SlewToAltAzAsync(double Azimuth, double Altitude) - { - tl.LogMessage("SlewToAltAzAsync", $"Az=~{Azimuth} Alt={Altitude}"); - _telescopeController.SlewToAltAzAsync(Azimuth, Altitude); - } - - public void SlewToCoordinates(double RightAscension, double Declination) - { - tl.LogMessage("SlewToCoordinates", $"Ra={RightAscension}, Dec={Declination}"); - _telescopeController.SlewToCoordinates(RightAscension, Declination); - } - - public void SlewToCoordinatesAsync(double RightAscension, double Declination) - { - tl.LogMessage("SlewToCoordinatesAsync", $"Ra={RightAscension}, Dec={Declination}"); - _telescopeController.SlewToCoordinatesAsync(RightAscension, Declination); - } - - public void SlewToTarget() - { - tl.LogMessage("SlewToTarget", "Executing"); - _telescopeController.SlewToTarget(); - } - - public void SlewToTargetAsync() - { - tl.LogMessage("SlewToTargetAsync", "Executing"); - _telescopeController.SlewToTargetAsync(); - } - - public bool Slewing - { - get - { - tl.LogMessage("Slewing Get", "Started"); - var result = _telescopeController.Slewing; - tl.LogMessage("Slewing Get", $"Result = {result}"); - return result; - } - } - - public void SyncToAltAz(double Azimuth, double Altitude) - { - tl.LogMessage("SyncToAltAz", "Not implemented"); - throw new ASCOM.MethodNotImplementedException("SyncToAltAz"); - } - - public void SyncToCoordinates(double RightAscension, double Declination) - { - tl.LogMessage("SyncToCoordinates", $"RA={RightAscension} Dec={Declination}"); - _telescopeController.TargetRightAscension = RightAscension; - _telescopeController.TargetDeclination = Declination; - - SyncToTarget(); - } - - public void SyncToTarget() - { - tl.LogMessage("SyncToTarget", "Executing"); - _telescopeController.SyncToTarget(); - } - - public double TargetDeclination - { - get - { - var targetDec = _telescopeController.TargetDeclination; - tl.LogMessage("TargetDeclination Get", $"{targetDec}"); - return targetDec; - } - set - { - tl.LogMessage("TargetDeclination Set", $"{value}"); - _telescopeController.TargetDeclination = value; - } - } - - public double TargetRightAscension - { - get - { - var targetRa = _telescopeController.TargetRightAscension; - tl.LogMessage("TargetRightAscension Get", $"{targetRa}"); - return targetRa; - } - set - { - tl.LogMessage("TargetRightAscension Set", $"{value}"); - _telescopeController.TargetRightAscension = value; - } - } - - private bool _tracking = true; - public bool Tracking - { - get - { - //todo implementing this, it exists. - - tl.LogMessage("Tracking", $"Get - {_tracking}" ); - return _tracking; - } - set - { - tl.LogMessage($"Tracking Set", $"{value}"); - _tracking = value; - } - } - - public DriveRates TrackingRate - { - get - { - var tr = _telescopeController.TrackingRate; - tl.LogMessage("TrackingRate Get", $"{tr}"); - return tr; - } - set - { - tl.LogMessage("TrackingRate Set", $"{value}"); - _telescopeController.TrackingRate = value; - } - } - - public ITrackingRates TrackingRates - { - get - { - ITrackingRates trackingRates = new TrackingRates(); - tl.LogMessage("TrackingRates", "Get - "); - foreach (DriveRates driveRate in trackingRates) - { - tl.LogMessage("TrackingRates", "Get - " + driveRate.ToString()); - } - return trackingRates; - } - } - - public DateTime UTCDate - { - get - { - tl.LogMessage("UTCDate", "Get started"); - - var utcDate = _telescopeController.utcDate; - tl.LogMessage("UTCDate", "Get - " + Format("MM/dd/yy HH:mm:ss", utcDate)); - return utcDate; - } - set - { - tl.LogMessage("UTCDate", "Set - " + Format("MM/dd/yy HH:mm:ss", value)); - _telescopeController.utcDate = value; - - } - } - - public void Unpark() - { - tl.LogMessage("Unpark", "Not implemented"); - throw new ASCOM.MethodNotImplementedException("Unpark"); - } - - #endregion - - #region IFocuser Implementation - - //private int focuserPosition = 0; // Class level variable to hold the current focuser position - //private const int focuserSteps = 10000; - - public bool Absolute - { - get - { - tl.LogMessage("Absolute Get", false.ToString()); - return false; // This is an absolute focuser - } - } - - public void Halt() - { - tl.LogMessage("Halt", "Halting"); - _telescopeController.FocuserHalt(); - } - - public bool IsMoving - { - get - { - tl.LogMessage("IsMoving Get", false.ToString()); - return false; // This focuser always moves instantaneously so no need for IsMoving ever to be True - } - } - - public bool Link - { - get - { - tl.LogMessage("Link Get", this.Connected.ToString()); - return this.Connected; // Direct function to the connected method, the Link method is just here for backwards compatibility - } - set - { - tl.LogMessage("Link Set", value.ToString()); - this.Connected = value; // Direct function to the connected method, the Link method is just here for backwards compatibility - } - } - - public int MaxIncrement - { - get - { - var maxIncrement = _telescopeController.FocuserMaxIncrement; - tl.LogMessage("MaxIncrement Get", maxIncrement.ToString()); - return maxIncrement; // Maximum change in one move - } - } - - public int MaxStep - { - get - { - var maxStep = _telescopeController.FocuserMaxStep; - tl.LogMessage("MaxStep Get", maxStep.ToString()); - return maxStep; - } - } - - public void Move(int Position) - { - tl.LogMessage("Move", Position.ToString()); - _telescopeController.FocuserMove(Position); - //focuserPosition = Position; // Set the focuser position - } - - public int Position - { - get - { - throw new ASCOM.PropertyNotImplementedException("Position", false); - //return focuserPosition; // Return the focuser position - } - } - - public double StepSize - { - get - { - tl.LogMessage("StepSize Get", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("StepSize", false); - } - } - - public bool TempComp - { - get - { - tl.LogMessage("TempComp Get", false.ToString()); - return false; - } - set - { - tl.LogMessage("TempComp Set", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("TempComp", false); - } - } - - public bool TempCompAvailable - { - get - { - tl.LogMessage("TempCompAvailable Get", false.ToString()); - return false; // Temperature compensation is not available in this driver - } - } - - public double Temperature - { - get - { - tl.LogMessage("Temperature Get", "Not implemented"); - throw new ASCOM.PropertyNotImplementedException("Temperature", false); - } - } - - #endregion - - #region Private properties and methods - // here are some useful properties and methods that can be used as required - // to help with driver development - - #region ASCOM Registration - - // Register or unregister driver for ASCOM. This is harmless if already - // registered or unregistered. - // - /// - /// 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 (var p = new ASCOM.Utilities.Profile()) - { - p.DeviceType = "Telescope"; - if (bRegister) - { - p.Register(driverID, driverDescription); - } - else - { - p.Unregister(driverID); - } - } - - using (var p = new ASCOM.Utilities.Profile()) - { - p.DeviceType = "Focuser"; - 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 => _telescopeController.Connected; - - /// - /// Use this function to throw an exception if we aren't connected to the hardware - /// - /// - private void CheckConnected(string message) - { - if (!IsConnected) - { - throw new ASCOM.NotConnectedException(message); - } - } - - /// - /// Read the device configuration from the ASCOM Profile store - /// - internal void ReadProfile() - { - using (Profile driverProfile = new Profile()) - { - driverProfile.DeviceType = "Telescope"; - tl.Enabled = Convert.ToBoolean(driverProfile.GetValue(driverID, traceStateProfileName, Empty, traceStateDefault)); - comPort = driverProfile.GetValue(driverID, comPortProfileName, Empty, comPortDefault); - } - } - - /// - /// Write the device configuration to the ASCOM Profile store - /// - internal void WriteProfile() - { - using (Profile driverProfile = new Profile()) - { - driverProfile.DeviceType = "Telescope"; - driverProfile.WriteValue(driverID, traceStateProfileName, tl.Enabled.ToString()); - driverProfile.WriteValue(driverID, comPortProfileName, comPort.ToString()); - } - } - - /// - /// Log helper function that takes formatted strings and arguments - /// - /// - /// - /// - internal static void LogMessage(string identifier, string message, params object[] args) - { - var msg = Format(message, args); - tl.LogMessage(identifier, msg); - } - #endregion - } -} diff --git a/MeadeAutostar497/Controller/FirmwareVersion.cs b/MeadeAutostar497/Controller/FirmwareVersion.cs deleted file mode 100644 index bdd0976..0000000 --- a/MeadeAutostar497/Controller/FirmwareVersion.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace ASCOM.MeadeAutostar497.Controller -{ - enum FirmwareVersion - { - autostar497_30eb, - autostar497_30ed, - autostar497_30ee, - autostar497_31ee, - autostar497_32ea, - //PEC added for Polar mounted scopes - - autostar497_32ee, - autostar497_32eh, - //Some serial strings fixed. - - autostar497_32ei, - autostar497_33ef, - //Some serial strings fixed. - - autostar497_33el, - autostar497_40eb, - autostar497_40ee, - autostar497_40ef, - autostar497_41ec, - autostar497_42ed, - //Get serial command for daylight savings (:GH# returns 0 for disabled 1 for enabled) - //Set serial command for daylight savings (:SH0# disables, :SH1# enables) - - autostar497_43ea, - autostar497_43ed, - autostar497_43eg - //Added :GW#, :AL#, :AA#, & :AP# - } -} \ No newline at end of file diff --git a/MeadeAutostar497/Controller/ISerialProcessor.cs b/MeadeAutostar497/Controller/ISerialProcessor.cs deleted file mode 100644 index 5578bf3..0000000 --- a/MeadeAutostar497/Controller/ISerialProcessor.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.IO.Ports; -using System.Runtime.InteropServices; - -namespace ASCOM.MeadeAutostar497.Controller -{ - [ComVisible(false)] - public interface ISerialProcessor - { - bool IsOpen { get; } - bool DtrEnable { get; set; } - bool RtsEnable { get; set; } - int BaudRate { get; set; } - int DataBits { get; set; } - StopBits StopBits { get; set; } - Parity Parity { get; set; } - string PortName { get; set; } - string[] GetPortNames(); - void Open(); - void Close(); - - string CommandTerminated(string command, string terminator); - char CommandChar(string command); - string ReadTerminated(string terminator); - void Command(string command); - void Lock(); - void Unlock(); - } -} \ No newline at end of file diff --git a/MeadeAutostar497/Controller/ITelescopeController.cs b/MeadeAutostar497/Controller/ITelescopeController.cs deleted file mode 100644 index 9873c48..0000000 --- a/MeadeAutostar497/Controller/ITelescopeController.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using ASCOM.DeviceInterface; - -namespace ASCOM.MeadeAutostar497.Controller -{ - public interface ITelescopeController - { - string Port { get; set; } - bool Connected { get; set; } - - bool Slewing { get; } - DateTime utcDate { get; set; } - double SiteLatitude { get; set; } - double SiteLongitude { get; set; } - AlignmentModes AlignmentMode { get; set; } - bool AtPark { get; } - double Altitude { get; } - double Azimuth { get; } - double RightAscension { get; } - double Declination { get; } - double TargetRightAscension { get; set; } - double TargetDeclination { get; set; } - DriveRates TrackingRate { get; set; } - int FocuserMaxIncrement { get; set; } - int FocuserMaxStep { get; set; } - void AbortSlew(); - void PulseGuide(GuideDirections direction, int duration); - void Park(); - void SlewToCoordinates(double rightAscension, double declination); - void SlewToCoordinatesAsync(double rightAscension, double declination); - void SlewToAltAz(double azimuth, double altitude); - void SlewToAltAzAsync(double azimuth, double altitude); - void SyncToTarget(); - void SlewToTarget(); - void SlewToTargetAsync(); - void MoveAxis(TelescopeAxes axis, double rate); - void FocuserHalt(); - void FocuserMove(int position); - string CommandString(string command, bool raw); - void CommandBlind(string command, bool raw); - } -} \ No newline at end of file diff --git a/MeadeAutostar497/Controller/SerialProcessor.cs b/MeadeAutostar497/Controller/SerialProcessor.cs deleted file mode 100644 index 1c77556..0000000 --- a/MeadeAutostar497/Controller/SerialProcessor.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.IO.Ports; -using System.Runtime.InteropServices; -using System.Threading; - -namespace ASCOM.MeadeAutostar497.Controller -{ - [ComVisible(false)] - public class SerialProcessor : ISerialProcessor - { - private SerialPort _serialPort = new SerialPort(); - private Mutex serialMutex = new Mutex(); - - public bool IsOpen => _serialPort.IsOpen; - - public bool DtrEnable - { - get => _serialPort.DtrEnable; - set => _serialPort.DtrEnable = value; - } - - public bool RtsEnable - { - get => _serialPort.RtsEnable; - set => _serialPort.RtsEnable = value; - } - - public int BaudRate - { - get => _serialPort.BaudRate; - set => _serialPort.BaudRate = value; - } - - public int DataBits - { - get => _serialPort.DataBits; - set => _serialPort.DataBits = value; - } - - public StopBits StopBits - { - get => _serialPort.StopBits; - set => _serialPort.StopBits = value; - } - - public Parity Parity - { - get => _serialPort.Parity; - set => _serialPort.Parity = value; - } - - public string PortName - { - get => _serialPort.PortName; - set => _serialPort.PortName = value; - } - - public string[] GetPortNames() - { - return SerialPort.GetPortNames(); - } - - public void Open() - { - _serialPort.ReadTimeout = 5000; - _serialPort.WriteTimeout = 5000; - _serialPort.Open(); - } - - public void Close() - { - _serialPort.Close(); - } - - public string CommandTerminated(string command, string terminator) - { - Lock(); - try - { - _serialPort.Write(command); - string result = _serialPort.ReadTo("#"); - return result; - } - finally - { - Unlock(); - } - } - - public char CommandChar(string command) - { - Lock(); - try - { - _serialPort.Write(command); - var result = _serialPort.ReadChar(); - return Convert.ToChar(result); - } - finally - { - Unlock(); - } - } - - public string ReadTerminated(string terminator) - { - Lock(); - try - { - string result = _serialPort.ReadTo("#"); - return result; - } - finally - { - Unlock(); - } - } - - public void Command(string command) - { - Lock(); - try - { - _serialPort.Write(command); - } - finally - { - Unlock(); - } - } - - public void Lock() - { - serialMutex.WaitOne(); - } - - public void Unlock() - { - serialMutex.ReleaseMutex(); - } - } -} diff --git a/MeadeAutostar497/Controller/TelescopeController.cs b/MeadeAutostar497/Controller/TelescopeController.cs deleted file mode 100644 index e314982..0000000 --- a/MeadeAutostar497/Controller/TelescopeController.cs +++ /dev/null @@ -1,992 +0,0 @@ -using System; -using System.IO.Ports; -using System.Linq; -using System.Threading; -using ASCOM.DeviceInterface; -using ASCOM.Utilities; -using ASCOM.Utilities.Interfaces; - -namespace ASCOM.MeadeAutostar497.Controller -{ - //todo stop this being a singleton, and instead use a server to make only a single instance. - public sealed class TelescopeController : ITelescopeController - { - private const double INVALID_PARAMETER = -1000; - - private static readonly Lazy Lazy = new Lazy(); - - public static TelescopeController Instance => Lazy.Value; - - //todo remove this as it can cause problems in production - private ISerialProcessor _serialPort; - public ISerialProcessor SerialPort - { - get => _serialPort ?? (_serialPort = new SerialProcessor()); - set - { - if (_serialPort == value) - return; - - if (_serialPort != null) - { - if (_serialPort.IsOpen) - throw new InvalidOperationException("Please disconnect before changing the serial engine."); - } - - _serialPort = value; - } - } - - private IUtil _util; - public IUtil Util - { - get => _util ?? (_util = new Util()); - set - { - if (Equals(_util, value)) - return; - - _util = value; - } - } - - private string _port = "COM1"; - public string Port - { - get => _port; - set - { - if (_port == value) return; - - if (Connected) - throw new InvalidOperationException("Please disconnect from the scope before changing port."); - - if (!ValidPort(value)) - throw new InvalidOperationException($"Unable to select port {value} as it does not exist."); - - _port = value; - } - } - - private bool ValidPort(string value) - { - return SerialPort.GetPortNames().Contains(value); - } - - public bool Connected - { - get => SerialPort.IsOpen; - set - { - if (value == Connected) - return; - - if (value) - { - //Connecting - try - { - AtPark = false; - SerialPort.DtrEnable = false; - SerialPort.RtsEnable = false; - SerialPort.BaudRate = 9600; - SerialPort.DataBits = 8; - SerialPort.StopBits = StopBits.One; - SerialPort.Parity = Parity.None; - SerialPort.PortName = Port; - SerialPort.Open(); - - TestConnectionActive(); - SetFocuserSpeedFastest(); - } - catch (Exception) - { - if (SerialPort.IsOpen) - SerialPort.Close(); - throw; - } - } - else - { - //Disconnecting - SerialPort.Close(); - AtPark = false; - } - } - } - - private void TestConnectionActive() - { - var firmwareVersionNumber = SerialPort.CommandTerminated(":GVN#", "#"); - if (string.IsNullOrEmpty(firmwareVersionNumber)) - { - throw new InvalidOperationException("Failed to communicate with telescope."); - } - } - - public bool Slewing - { - get - { - if (!Connected) return false; - - - if (movingAxis()) - return true; - - var result = SerialPort.CommandTerminated(":D#", "#"); - return result != string.Empty; - } - } - - public DateTime utcDate - { - get - { - string telescopeDate = SerialPort.CommandTerminated(":GC#", "#"); - string telescopeTime = SerialPort.CommandTerminated(":GL#", "#"); - - int month = telescopeDate.Substring(0, 2).ToInteger(); - int day = telescopeDate.Substring(3, 2).ToInteger(); - int year = telescopeDate.Substring(6, 2).ToInteger(); - - if (year < 2000) //todo fix this hack that will create a Y2K100 bug - { - year = year + 2000; - } - - int hour = telescopeTime.Substring(0, 2).ToInteger(); - int minute = telescopeTime.Substring(3, 2).ToInteger(); - int second = telescopeTime.Substring(6, 2).ToInteger(); - - var utcCorrection = GetUtcCorrection(); - - //Todo is this telescope local time, or real utc? - var newDate = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc) + utcCorrection; - - return newDate; - } - set - { - var utcCorrection = GetUtcCorrection(); - var localDateTime = value - utcCorrection; - - //Todo is this telescope local time, or real utc? - var timeResult = SerialPort.CommandChar($":SL{localDateTime:HH:mm:ss}#"); - if (timeResult != '1') - { - throw new InvalidOperationException("Failed to set local time"); - } - - SerialPort.Lock(); - try - { - var dateResult = SerialPort.CommandChar($":SC{localDateTime:MM/dd/yy}#"); - if (dateResult != '1') - { - throw new InvalidOperationException("Failed to set local date"); - } - - //throwing away these two strings which represent - SerialPort.ReadTerminated("#"); //Updating Planetary Data# - SerialPort.ReadTerminated("#"); // # - } - finally - { - SerialPort.Unlock(); - } - } - - } - - private TimeSpan GetUtcCorrection() - { - string utcOffSet = SerialPort.CommandTerminated(":GG#", "#"); - double utcOffsetHours = double.Parse(utcOffSet); - TimeSpan utcCorrection = TimeSpan.FromHours(utcOffsetHours); - return utcCorrection; - } - - public double SiteLatitude - { - get - { - var latitude = SerialPort.CommandTerminated( ":Gt#", "#"); - - return DmsToDouble(latitude); - } - 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."); - - int d = Convert.ToInt32(Math.Floor(value)); - int m = Convert.ToInt32(60 * (value - d)); - - var result = SerialPort.CommandChar($":Sts{d:00}*{m:00}#"); - if (result != '1') - throw new InvalidOperationException("Failed to set site latitude."); - } - } - - private double DmsToDouble(string dms) - { - if (IsNumeric(dms[0])) - { - double l = int.Parse(dms.Substring(0, 3)); - l = l + double.Parse(dms.Substring(4, 2)) / 60; - if (dms.Length == 9) - l = l + double.Parse(dms.Substring(7, 2)) / 60 / 60; - - return l; - } - - double lat = int.Parse(dms.Substring(1, 2)); - lat = lat + double.Parse(dms.Substring(4, 2)) / 60; - if (dms.Length == 9) - lat = lat + double.Parse(dms.Substring(7, 2)) / 60 / 60; - - if (dms[0] == '-') - lat = -lat; - - return lat; - } - - private bool IsNumeric(char c) - { - char[] nums = new[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; - return nums.Contains(c); - } - - public double SiteLongitude - { - get - { - var longitude = SerialPort.CommandTerminated(":Gg#", "#"); - double l = DmsToDouble(longitude); - - if (l > 180) - l = l - 360; - - return l; - } - set - { - if (value > 180) - throw new InvalidValueException("Longitude cannot be greater than 180 degrees."); - - if (value < -180) - throw new InvalidValueException("Longitude cannot be lower than -180 degrees."); - - int d = Convert.ToInt32(Math.Floor(value)); - int m = Convert.ToInt32(60 * (value - d)); - - var result = SerialPort.CommandChar($":Sg{d:000}*{m:00}#"); - if (result != '1') - throw new InvalidOperationException("Failed to set site longitude."); - } - - } - - public AlignmentModes AlignmentMode - { - get - { - const char ack = (char)6; - //var alignmentString = SerialPort.CommandTerminated(":GW#", "#"); - var alignmentString = SerialPort.CommandChar(ack.ToString()); - //: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. - - switch (alignmentString) - { - case 'A': return AlignmentModes.algAltAz; - case 'P': return AlignmentModes.algPolar; - case 'G': return AlignmentModes.algGermanPolar; - default: - throw new InvalidValueException($"unknown alignment returned from telescope: {alignmentString}"); - } - } - set - { - switch (value) - { - case AlignmentModes.algAltAz: - SerialPort.Command(":AA#"); - //:AA# Sets telescope the AltAz alignment mode - //Returns: nothing - break; - case AlignmentModes.algPolar: - case AlignmentModes.algGermanPolar: - SerialPort.Command(":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 bool AtPark { get; private set; } - - public double Azimuth - { - get - { - var result = SerialPort.CommandTerminated(":GZ#", "#"); - //:GZ# Get telescope azimuth - //Returns: DDD*MM#T or DDD*MM’SS# - //The current telescope Azimuth depending on the selected precision. - - double az = DmsToDouble(result); - - return az; - } - } - - public double RightAscension { - get - { - var result = SerialPort.CommandTerminated(":GR#", "#"); - //:GR# Get Telescope RA - //Returns: HH: MM.T# or HH:MM:SS# - //Depending which precision is set for the telescope - - double ra = HmsToDouble(result); - - return ra; - } - } - - private double HmsToDouble(string hms) - { - return Util.HMSToHours(hms); - } - - public double Declination - { - get - { - var result = SerialPort.CommandTerminated(":GD#", "#"); - //:GD# Get Telescope Declination. - //Returns: sDD* MM# or sDD*MM’SS# - //Depending upon the current precision setting for the telescope. - - double az = DmsToDouble(result); - - return az; - } - } - - private double _targetRightAscension = INVALID_PARAMETER; - public double TargetRightAscension { - get - { - if (_targetRightAscension == INVALID_PARAMETER) - throw new ASCOM.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; - return _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 = Util.HoursToHMS(value, ":", ":", ":", 2); - var response = SerialPort.CommandChar($":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 double _targetDeclination = INVALID_PARAMETER; - public double TargetDeclination { - get - { - if (_targetDeclination == INVALID_PARAMETER) - throw new ASCOM.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; - return _targetDeclination; - } - set - { - //todo implement low precision version of this. - if (value > 90) - throw new ASCOM.InvalidValueException("Declination cannot be greater than 90."); - - if (value < -90) - throw new ASCOM.InvalidValueException("Declination cannot be less than -90."); - - - var dms = Util.DegreesToDMS(value, "*", ":", ":", 2); - var s = value < 0 ? '-' : '+'; - - var result = SerialPort.CommandChar($":Sd{s}{dms}#"); - //: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 ASCOM.InvalidOperationException("Target declination invalid"); - } - - _targetDeclination = value; - } - } - - private DriveRates _trackingRate = DriveRates.driveSidereal; - public DriveRates TrackingRate - { - get - { - //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; - return _trackingRate; - } - set - { - switch (value) - { - case DriveRates.driveSidereal: - SerialPort.Command(":TQ#"); - //:TQ# Selects sidereal tracking rate - //Returns: Nothing - break; - case DriveRates.driveLunar: - SerialPort.Command(":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 int FocuserMaxIncrement { get; set; } = 7000; - - public int FocuserMaxStep { get; set; } = 7000; - - public double Altitude { - get - { - var result = SerialPort.CommandTerminated(":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. - return DmsToDouble(result); - } - } - - public void AbortSlew() - { - SerialPort.Command("#:Q#"); - } - - public void PulseGuide(GuideDirections direction, int duration) - { - 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; - } - - if (UserNewerPulseGuiding) - { - _serialPort.Command($":Mg{d}{duration:0000}#"); - Thread.Sleep(duration); //todo figure out if this is really needed - } - else - { - SerialPort.Lock(); - try - { - _serialPort.Command(":RG#"); //Make sure we are at guide rate - _serialPort.Command($":M{d}#"); - Thread.Sleep(duration); - _serialPort.Command($":Q{d}#"); - - //classic only !!!, this is needed since once in a while one is not enough - Thread.Sleep(200); - _serialPort.Command($":Q{d}#"); - } - finally - { - SerialPort.Unlock(); - } - } - } - - public void Park() - { - if (AtPark) - return; - - AtPark = true; - _serialPort.Command(":hP#"); - } - - public void SlewToCoordinates(double rightAscension, double declination) - { - SlewToCoordinatesAsync(rightAscension, declination); - - while (Slewing) //wait for slew to complete - { - Util.WaitForMilliseconds(200); //be responsive to AbortSlew(); - } - } - - public void SlewToCoordinatesAsync(double rightAscension, double declination) - { - SerialPort.Lock(); - try - { - TargetRightAscension = rightAscension; - TargetDeclination = declination; - - DoSlewAsync(true); - } - finally - { - SerialPort.Unlock(); - } - } - - public void SlewToAltAz(double azimuth, double altitude) - { - SlewToAltAzAsync(azimuth, altitude); - - while (Slewing) //wait for slew to complete - { - Util.WaitForMilliseconds(200); //be responsive to AbortSlew(); - } - } - - private double TargetAltitude - { - set - { - if (value > 90) - throw new ASCOM.InvalidValueException("Altitude cannot be greater than 90."); - - if (value < 0) - throw new ASCOM.InvalidValueException("Altitide cannot be less than 0."); - - - var dms = Util.DegreesToDMS(value, "*", "'", ".", 2); - var s = value < 0 ? '-' : '+'; - - var result = SerialPort.CommandChar($":Sa{s}{dms}#"); - //:SasDD*MM# - //Set target object altitude to sDD*MM# or sDD*MM’SS# [LX 16”, Autostar, Autostar II] - //Returns: - //1 Object within slew range - //0 Object out of slew range - - if (result == '0') - throw new ASCOM.InvalidOperationException("Target altitude out of slew range"); - } - } - - private double TargetAzimuth - { - set - { - if (value >= 360) - throw new ASCOM.InvalidValueException("Azimuth cannot be 360 or higher."); - - if (value < 0) - throw new ASCOM.InvalidValueException("Azimuth cannot be less than 0."); - - var dms = Util.DegreesToDM(value, "*", ":", 2); - - var result = SerialPort.CommandChar($":Sd{dms}#"); - //:SzDDD*MM# - //Sets the target Object Azimuth[LX 16” and Autostar II only] - //Returns: - //0 – Invalid - //1 - Valid - - if (result == '0') - throw new ASCOM.InvalidOperationException("Target Azimuth out of slew range"); - - } - } - - public void SlewToAltAzAsync(double azimuth, double altitude) - { - SerialPort.Lock(); - try - { - TargetAltitude = altitude; - TargetAzimuth = azimuth; - - DoSlewAsync(false); - } - finally - { - SerialPort.Unlock(); - } - } - - public void SyncToTarget() - { - var result = SerialPort.CommandTerminated(":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 - At static string: " M31 EX GAL MAG 3.5 SZ178.0'#" - - if (result == string.Empty) - throw new ASCOM.InvalidOperationException("Unable to perform sync"); - } - - public void SlewToTarget() - { - SlewToTargetAsync(); - - while (Slewing) - { - Util.WaitForMilliseconds(200); - } - } - - public void SlewToTargetAsync() - { - if (TargetDeclination == INVALID_PARAMETER || TargetRightAscension == INVALID_PARAMETER ) - throw new ASCOM.InvalidOperationException("No target selected to slew to."); - - DoSlewAsync(true); - } - - private bool movingAxis() - { - return _movingPrimary || _movingSecondary; - } - - private bool _movingPrimary; - private bool _movingSecondary; - - public void MoveAxis(TelescopeAxes axis, double rate) - { - SerialPort.Lock(); - try - { - 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: - SerialPort.Command(":RG#"); - //:RG# Set Slew rate to Guiding Rate (slowest) - //Returns: Nothing - break; - case 2: - SerialPort.Command(":RC#"); - //:RC# Set Slew rate to Centering rate (2nd slowest) - //Returns: Nothing - break; - case 3: - SerialPort.Command(":RM#"); - //:RM# Set Slew rate to Find Rate (2nd Fastest) - //Returns: Nothing - break; - case 4: - SerialPort.Command(":RS#"); - //:RS# Set Slew rate to max (fastest) - //Returns: Nothing - break; - default: - throw new ASCOM.InvalidValueException($"Rate {rate} not supported"); - - } - - switch (axis) - { - case TelescopeAxes.axisPrimary: - if (rate == 0) - { - _movingPrimary = false; - SerialPort.Command(":Qe#"); - //:Qe# Halt eastward Slews - //Returns: Nothing - SerialPort.Command(":Qw#"); - //:Qw# Halt westward Slews - //Returns: Nothing - } - else if (rate > 0) - { - SerialPort.Command(":Me#"); - //:Me# Move Telescope East at current slew rate - //Returns: Nothing - _movingPrimary = true; - } - else - { - SerialPort.Command(":Mw#"); - //:Mw# Move Telescope West at current slew rate - //Returns: Nothing - _movingPrimary = true; - } - - break; - case TelescopeAxes.axisSecondary: - if (rate == 0) - { - _movingSecondary = false; - SerialPort.Command(":Qn#"); - //:Qn# Halt northward Slews - //Returns: Nothing - SerialPort.Command(":Qs#"); - //:Qs# Halt southward Slews - //Returns: Nothing - } - else if (rate > 0) - { - SerialPort.Command(":Mn#"); - //:Mn# Move Telescope North at current slew rate - //Returns: Nothing - _movingSecondary = true; - } - else - { - SerialPort.Command(":Ms#"); - //:Ms# Move Telescope South at current slew rate - //Returns: Nothing - _movingSecondary = true; - } - - break; - default: - throw new ASCOM.MethodNotImplementedException("Can not move this axis."); - } - } - finally - { - SerialPort.Unlock(); - } - } - - public void FocuserHalt() - { - SerialPort.Command(":FQ#"); - //:FQ# Halt Focuser Motion - //Returns: Nothing - } - - public void FocuserMove(int newPosition) - { - //todo implement backlash compensation - //todo implement direction reverse - //todo implement dynamic braking - - if (newPosition < -FocuserMaxIncrement || newPosition > FocuserMaxIncrement) - { - throw new ASCOM.InvalidValueException($"position out of range {-FocuserMaxIncrement} < {newPosition} < {FocuserMaxIncrement}"); - } - - if (newPosition == 0) - return; - - if (newPosition > 0) - { - //desired move direction is out - MoveFocuser(true, Math.Abs(newPosition)); - } - else - { - //desired move direction is in - MoveFocuser(false, Math.Abs(newPosition)); - } - } - - public string CommandString(string command, bool raw) - { - return SerialPort.CommandTerminated(command, "#"); - } - - public void CommandBlind(string command, bool raw) - { - SerialPort.Command(command); - } - - private void MoveFocuser(bool directionOut, int steps) - { - SerialPort.Command(directionOut ? ":F+#" : ":F-#"); - //:F+# Start Focuser moving inward (toward objective) - //Returns: None - - //:F-# Start Focuser moving outward (away from objective) - //Returns: None - - Util.WaitForMilliseconds(steps); - - FocuserHalt(); - } - - private void SetFocuserSpeedFastest() - { - SerialPort.Command(":FF#"); - //:FF# Set Focus speed to fastest setting - //Returns: Nothing - } - - private void SetFocuserSpeedSlowest() - { - SerialPort.Command(":FS#"); - //:FS# Set Focus speed to slowest setting - //Returns: Nothing - } - - private void SetFocuserSpeed( int speed) - { - if (speed < 1) - throw new ArgumentOutOfRangeException("speed is too low"); - - if (speed > 4) - throw new ArgumentOutOfRangeException("speed is too high"); - - SerialPort.Command($":F{speed}#"); - //:F# Autostar, Autostar II – set focuser speed to where is an ASCII digit 1..4 - //Returns: Nothing - //All others – Not Supported - } - - //todo remove the polar parameter and split method into two. - private void DoSlewAsync( bool polar) - { - SerialPort.Lock(); - try - { - switch (polar) - { - case true: - var response = SerialPort.CommandChar(":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. - break; - case '1': - //Below Horizon - string belowHorizonMessage = SerialPort.ReadTerminated("#"); - throw new ASCOM.InvalidOperationException(belowHorizonMessage); - case '2': - //Below Horizon - string belowMinimumElevationMessage = SerialPort.ReadTerminated("#"); - throw new ASCOM.InvalidOperationException(belowMinimumElevationMessage); - default: - throw new ASCOM.DriverException("This error should not happen"); - - } - - break; - case false: - var maResponse = SerialPort.CommandChar(":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 ASCOM.InvalidOperationException("fault"); - } - - break; - } - } - finally - { - SerialPort.Unlock(); - } - } - - public bool UserNewerPulseGuiding { get; set; } = true; //todo make this a device setting - } -} diff --git a/TargetToBeat.txt b/TargetToBeat.txt deleted file mode 100644 index 8255bd9..0000000 --- a/TargetToBeat.txt +++ /dev/null @@ -1,249 +0,0 @@ - -ConformanceCheck ASCOM Device Conformance Checker Version 6.4.63.0, Build time: 18/12/2018 08:58:34 -ConformanceCheck Running on: ASCOM Platform 6.4 SP1 6.4.1.2695 - -ConformanceCheck Driver ProgID: MeadeEx.Telescope - -Error handling -Error number for "Not Implemented" is: 80040400 -Error number for "Invalid Value 1" is: 80040401 -Error number for "Invalid Value 2" is: 80040405 -Error number for "Value Not Set 1" is: 80040402 -Error number for "Value Not Set 2" is: 80040403 -Error messages will not be interpreted to infer state. - -21:04:42.255 Driver Access Checks OK -21:04:42.997 AccessChecks OK Successfully created driver using late binding -21:04:50.059 AccessChecks OK Successfully connected using late binding -21:04:50.063 AccessChecks INFO The driver is a COM object -21:04:57.769 AccessChecks INFO Device exposes interface ITelescopeV2 -21:04:58.546 AccessChecks INFO Device does not expose interface ITelescopeV3 -21:04:59.881 AccessChecks OK Successfully created driver using driver access toolkit -21:05:06.873 AccessChecks OK Successfully connected using driver access toolkit - -Conform is using ASCOM.DriverAccess.Telescope to get a Telescope object -21:05:08.261 ConformanceCheck OK Driver instance created successfully -21:05:15.293 ConformanceCheck OK Connected OK - -Common Driver Methods -21:05:15.333 InterfaceVersion OK 2 -21:05:15.362 Connected OK True -21:05:15.448 Description OK Meade Autostar 497: ETX Autostar|A|43Ea|Jun 02 2006@10:09:40 -21:05:15.480 DriverInfo OK ASCOM Meade Telescope/Focuser driver for classic and Autostar I 5.0.4 - ASCOM Iniative - http://ascom-standards.org/ - Last Modified 10/06/2013 19:06:54 -21:05:15.509 DriverVersion OK 5.0 -21:05:15.539 Name OK Autostar 497 -21:05:15.568 CommandString INFO Conform cannot test the CommandString method -21:05:15.572 CommandBlind INFO Conform cannot test the CommandBlind method -21:05:15.582 CommandBool INFO Conform cannot test the CommandBool method -21:05:15.588 Action INFO Conform cannot test the Action method -21:05:15.595 SupportedActions OK Driver returned an empty action list - -Can Properties -21:05:15.663 CanFindHome OK False -21:05:15.671 CanPark OK True -21:05:15.679 CanPulseGuide OK True -21:05:15.687 CanSetDeclinationRate OK False -21:05:15.695 CanSetGuideRates OK False -21:05:15.703 CanSetPark OK False -21:05:15.711 CanSetPierSide OK False -21:05:15.719 CanSetRightAscensionRate OK False -21:05:15.727 CanSetTracking OK False -21:05:15.735 CanSlew OK True -21:05:15.743 CanSlewltAz OK True -21:05:15.752 CanSlewAltAzAsync OK True -21:05:15.761 CanSlewAsync OK True -21:05:15.769 CanSync OK True -21:05:15.777 CanSyncAltAz OK False -21:05:15.785 CanUnPark OK False - -Pre-run Checks -21:05:15.834 Mount Safety INFO Scope is not parked, continuing testing -21:05:15.887 TimeCheck INFO PC Time Zone: GMT Summer Time, offset -1 hours. -21:05:15.894 TimeCheck INFO PC UTCDate: 30-Apr-2019 20:05:15.894 -21:05:16.013 TimeCheck INFO Mount UTCDate: 30-Apr-2019 20:04:15.000 - -Properties -21:05:16.063 AlignmentMode OK algPolar -21:05:16.117 Altitude OK 90.00 -21:05:16.149 ApertureArea ERROR Unexpected DriverAccessCOMException, : Property ApertureAreaThe supplied value is out of range for this property. -21:05:16.182 ApertureDiameter ERROR Unexpected DriverAccessCOMException, : Property ApertureDiameterThe supplied value is out of range for this property. -21:05:16.214 AtHome OK False -21:05:16.245 AtPark OK False -21:05:16.310 Azimuth OK 0.57 -21:05:16.369 Declination OK 89:59:59.00 -21:05:16.402 DeclinationRate Read OK 0.00 -21:05:16.434 DeclinationRate Write OK CanSetDeclinationRate is False and a PropertyNotImplementedException exception was generated as expected -21:05:16.469 DoesRefraction Read OK True -21:05:16.501 DoesRefraction Write ERROR Unexpected DriverAccessCOMException, : Property DoesRefractionThe supplied value is out of range for this property. -21:05:16.534 EquatorialSystem OK equLocalTopocentric -21:05:16.568 FocalLength ERROR Unexpected DriverAccessCOMException, : Property FocalLengthThe supplied value is out of range for this property. -21:05:16.602 GuideRateDeclination Read OK 0.00 -21:05:16.613 GuideRateDeclination Write OK CanSetGuideRates is False and a PropertyNotImplementedException exception was generated as expected -21:05:16.646 GuideRateRightAscension Read OK 0.00 -21:05:16.661 GuideRateRightAscension Write OK CanSetGuideRates is False and a PropertyNotImplementedException exception was generated as expected -21:05:16.694 IsPulseGuiding OK False -21:05:16.748 RightAscension OK 22:28:48.00 -21:05:16.781 RightAscensionRate Read OK 0.00 -21:05:16.814 RightAscensionRate Write OK CanSetRightAscensionRate is False and a PropertyNotImplementedException exception was generated as expected -21:05:16.848 SiteElevation Read OK 0 -21:05:16.884 SiteElevation Write ERROR Unexpected DriverAccessCOMException, : Property SiteElevation The supplied value is out of range for this property. -21:05:16.896 SiteElevation Write ERROR Unexpected DriverAccessCOMException, : Property SiteElevation The supplied value is out of range for this property. -21:05:16.908 SiteElevation Write OK Legal value 0m written successfully -21:05:16.965 SiteLatitude Read OK 53:50:00.00 -21:05:16.999 SiteLatitude Write OK Optional member threw a PropertyNotImplementedException exception. -21:05:17.012 SiteLatitude Write OK Optional member threw a PropertyNotImplementedException exception. -21:05:17.025 SiteLatitude Write OK Optional member threw a PropertyNotImplementedException exception. -21:05:17.096 SiteLongitude Read OK -01:46:00.00 -21:05:17.130 SiteLongitude Write OK Optional member threw a PropertyNotImplementedException exception. -21:05:17.144 SiteLongitude Write OK Optional member threw a PropertyNotImplementedException exception. -21:05:17.156 SiteLongitude Write OK Optional member threw a PropertyNotImplementedException exception. -21:05:17.192 Slewing OK False -21:05:17.226 SlewSettleTime Read OK 0 -21:05:17.262 SlewSettleTime Write ERROR Unexpected DriverAccessCOMException, : Property SlewSettleTime The supplied value is out of range for this property. -21:05:17.277 SlewSettleTime Write OK Legal value 0 seconds written successfully -21:05:17.313 SideOfPier Read OK Optional member threw a PropertyNotImplementedException exception. -21:05:17.356 SiderealTime OK 10:31:06.00 -21:05:17.368 SiderealTime OK Scope and ASCOM sidereal times agree to better than 5 minutes, Scope: 10:31:06.00, ASCOM: 10:32:07.97 -21:05:17.405 TargetDeclination Read ERROR Unexpected DriverAccessCOMException, : The target value is not set. -21:05:17.440 TargetDeclination Write INFO Tests moved after the SlewToCoordinates tests so that Conform can check they properly set target coordinates. -21:05:17.454 TargetRightAscension Read ERROR Unexpected DriverAccessCOMException, : The target value is not set. -21:05:17.489 TargetRightAscension Write INFO Tests moved after the SlewToCoordinates tests so that Conform can check they properly set target coordinates. -21:05:17.502 Tracking Read OK True -21:05:17.537 Tracking Write OK CanSetTracking is False and a PropertyNotImplementedException exception was generated as expected -21:05:17.575 TrackingRates ERROR Unexpected System.Reflection.TargetInvocationException exception, : Exception has been thrown by the target of an invocation. -21:05:17.590 TrackingRates ERROR Unexpected System.Reflection.TargetInvocationException exception, : Exception has been thrown by the target of an invocation. -21:05:17.603 TrackingRates OK Dispose member not present -21:05:17.638 TrackingRates OK Successfully obtained a TrackingRates object after the previous TrackingRates object was disposed -21:05:17.651 TrackingRate Read OK driveSidereal -21:05:17.665 TrackingRate Write ERROR Unexpected System.Reflection.TargetInvocationException exception, : Exception has been thrown by the target of an invocation. -21:05:17.760 UTCDate Read OK 30-Apr-2019 20:04:16.000 -21:05:17.774 UTCDate Write OK Optional member threw a PropertyNotImplementedException exception. - -Methods -21:05:17.858 CanMoveAxis:Primary OK CanMoveAxis:Primary False -21:05:17.894 CanMoveAxis:Secondary OK CanMoveAxis:Secondary False -21:05:17.931 CanMoveAxis:Tertiary OK CanMoveAxis:Tertiary False -21:05:17.967 Park/Unpark INFO Tests skipped -21:05:18.981 AbortSlew OK AbortSlew OK when not slewing -21:05:19.040 AxisRate:Primary Enum ERROR .NET - Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Runtime.InteropServices.COMException: Member not found. (Exception from HRESULT: 0x80020003 (DISP_E_MEMBERNOTFOUND)) - --- End of inner exception stack trace --- - at Microsoft.VisualBasic.CompilerServices.Symbols.Container.InvokeMethod(Method TargetProcedure, Object[] Arguments, Boolean[] CopyBack, BindingFlags Flags) - at Microsoft.VisualBasic.CompilerServices.NewLateBinding.ObjectLateGet(Object Instance, Type Type, String MemberName, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, Boolean[] CopyBack) - at Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateGet(Object Instance, Type Type, String MemberName, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, Boolean[] CopyBack) - at Conform.TelescopeTester.TelescopeAxisRateTest(String p_Name, TelescopeAxes p_Axis) in J:\Conform\Conform\Devices\TelescopeTester.vb:line 3022 -21:05:19.055 AxisRate:Primary OK Empty axis rate returned -21:05:19.069 AxisRate:Primary OK AxisRates.Dispose() member not present for axis axisPrimary -21:05:19.085 AxisRate:Secondary Enum ERROR .NET - Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Runtime.InteropServices.COMException: Member not found. (Exception from HRESULT: 0x80020003 (DISP_E_MEMBERNOTFOUND)) - --- End of inner exception stack trace --- - at Microsoft.VisualBasic.CompilerServices.Symbols.Container.InvokeMethod(Method TargetProcedure, Object[] Arguments, Boolean[] CopyBack, BindingFlags Flags) - at Microsoft.VisualBasic.CompilerServices.NewLateBinding.ObjectLateGet(Object Instance, Type Type, String MemberName, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, Boolean[] CopyBack) - at Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateGet(Object Instance, Type Type, String MemberName, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, Boolean[] CopyBack) - at Conform.TelescopeTester.TelescopeAxisRateTest(String p_Name, TelescopeAxes p_Axis) in J:\Conform\Conform\Devices\TelescopeTester.vb:line 3022 -21:05:19.100 AxisRate:Secondary OK Empty axis rate returned -21:05:19.117 AxisRate:Secondary OK AxisRates.Dispose() member not present for axis axisSecondary -21:05:19.132 AxisRate:Tertiary Enum ERROR .NET - Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Runtime.InteropServices.COMException: Member not found. (Exception from HRESULT: 0x80020003 (DISP_E_MEMBERNOTFOUND)) - --- End of inner exception stack trace --- - at Microsoft.VisualBasic.CompilerServices.Symbols.Container.InvokeMethod(Method TargetProcedure, Object[] Arguments, Boolean[] CopyBack, BindingFlags Flags) - at Microsoft.VisualBasic.CompilerServices.NewLateBinding.ObjectLateGet(Object Instance, Type Type, String MemberName, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, Boolean[] CopyBack) - at Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateGet(Object Instance, Type Type, String MemberName, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, Boolean[] CopyBack) - at Conform.TelescopeTester.TelescopeAxisRateTest(String p_Name, TelescopeAxes p_Axis) in J:\Conform\Conform\Devices\TelescopeTester.vb:line 3022 -21:05:19.149 AxisRate:Tertiary OK Empty axis rate returned -21:05:19.178 AxisRate:Tertiary OK AxisRates.Dispose() member not present for axis axisTertiary -21:05:19.195 FindHome OK CanFindHome is False and a MethodNotImplementedException exception was generated as expected -21:05:19.234 MoveAxis Primary OK CanMoveAxis Primary is False and a MethodNotImplementedException exception was generated as expected -21:05:19.273 MoveAxis Secondary OK CanMoveAxis Secondary is False and a MethodNotImplementedException exception was generated as expected -21:05:19.314 MoveAxis Tertiary OK CanMoveAxis Tertiary is False and a MethodNotImplementedException exception was generated as expected -21:05:21.349 PulseGuide OK Synchronous pulse guide found OK -21:06:13.942 SlewToCoordinates INFO Slewed within 15.0 arc seconds of expected RA: 09:31:10.00, actual RA: 09:31:09.00 -21:06:13.956 SlewToCoordinates OK Slewed OK. DEC: 01:00:00.00 -21:06:13.971 SlewToCoordinates OK The TargetRightAscension property 09:31:10.00 matches the expected RA OK. -21:06:13.987 SlewToCoordinates OK The TargetDeclination property 01:00:00.00 matches the expected Declination OK. -21:06:14.028 SlewToCoordinates (Bad L) ERROR Unexpected DriverAccessCOMException, slewing to bad RA coordinate: Property TargetRightAscension The supplied value is out of range for this property. -21:06:14.102 SlewToCoordinates (Bad L) ERROR Unexpected DriverAccessCOMException, slewing to bad Dec coordinate: Property TargetDeclination The supplied value is out of range for this property. -21:06:14.164 SlewToCoordinates (Bad H) ERROR Unexpected DriverAccessCOMException, slewing to bad RA coordinate: Property TargetRightAscension The supplied value is out of range for this property. -21:06:14.262 SlewToCoordinates (Bad H) ERROR Unexpected DriverAccessCOMException, slewing to bad Dec coordinate: Property TargetDeclination The supplied value is out of range for this property. -21:06:48.075 SlewToCoordinatesAsync INFO Slewed within 45.0 arc seconds of expected RA: 08:32:03.00, actual RA: 08:32:00.00 -21:06:48.090 SlewToCoordinatesAsync OK Slewed OK. DEC: 02:00:00.00 -21:06:48.106 SlewToCoordinatesAsync OK The TargetRightAscension property 08:32:03.00 matches the expected RA OK. -21:06:48.122 SlewToCoordinatesAsync OK The TargetDeclination property 02:00:00.00 matches the expected Declination OK. -21:06:48.164 SlewToCoordinatesAsync (Bad L) ERROR Unexpected DriverAccessCOMException, slewing to bad RA coordinate: Property TargetRightAscension The supplied value is out of range for this property. -21:06:48.239 SlewToCoordinatesAsync (Bad L) ERROR Unexpected DriverAccessCOMException, slewing to bad Dec coordinate: Property TargetDeclination The supplied value is out of range for this property. -21:06:48.302 SlewToCoordinatesAsync (Bad H) ERROR Unexpected DriverAccessCOMException, slewing to bad RA coordinate: Property TargetRightAscension The supplied value is out of range for this property. -21:06:48.436 SlewToCoordinatesAsync (Bad H) ERROR Unexpected DriverAccessCOMException, slewing to bad Dec coordinate: Property TargetDeclination The supplied value is out of range for this property. -21:07:22.044 SyncToCoordinates INFO Slewed to start position within 15.0 arc seconds of expected RA: 07:32:37.00, actual RA: 07:32:36.00 -21:07:22.061 SyncToCoordinates OK Slewed to start position OK. DEC: 26:55:00.00 -21:07:22.782 SyncToCoordinates INFO Synced to sync position within 15.0 arc seconds of expected RA: 07:28:37.00, actual RA: 07:28:36.00 -21:07:22.796 SyncToCoordinates OK Synced to sync position OK. DEC: 25:55:00.00 -21:07:22.812 SyncToCoordinates OK The TargetRightAscension property 07:28:37.00 matches the expected RA OK. -21:07:22.828 SyncToCoordinates OK The TargetDeclination property 25:55:00.00 matches the expected Declination OK. -21:07:53.961 SyncToCoordinates INFO Slewed back to start position within 15.0 arc seconds of expected RA: 07:32:37.00, actual RA: 07:32:36.00 -21:07:53.976 SyncToCoordinates OK Slewed back to start position OK. DEC: 26:55:00.00 -21:07:54.692 SyncToCoordinates INFO Synced to reversed sync position within 30.0 arc seconds of expected RA: 07:36:37.00, actual RA: 07:36:35.00 -21:07:54.708 SyncToCoordinates OK Synced to reversed sync position OK. DEC: 27:55:00.00 -21:08:25.954 SyncToCoordinates INFO Slewed back to start position within 15.0 arc seconds of expected RA: 07:32:37.00, actual RA: 07:32:36.00 -21:08:25.969 SyncToCoordinates OK Slewed back to start position OK. DEC: 26:55:00.00 -21:08:26.009 SyncToCoordinates (Bad L) ERROR Unexpected DriverAccessCOMException, syncing to bad RA coordinate: Property TargetRightAscension The supplied value is out of range for this property. -21:08:26.087 SyncToCoordinates (Bad L) ERROR Unexpected DriverAccessCOMException, syncing to bad Dec coordinate: Property TargetDeclination The supplied value is out of range for this property. -21:08:26.150 SyncToCoordinates (Bad H) ERROR Unexpected DriverAccessCOMException, syncing to bad RA coordinate: Property TargetRightAscension The supplied value is out of range for this property. -21:08:26.229 SyncToCoordinates (Bad H) ERROR Unexpected DriverAccessCOMException, syncing to bad Dec coordinate: Property TargetDeclination The supplied value is out of range for this property. -21:08:26.292 TargetRightAscension Write ERROR Unexpected DriverAccessCOMException, : Property TargetRightAscension The supplied value is out of range for this property. -21:08:26.311 TargetRightAscension Write ERROR Unexpected DriverAccessCOMException, : Property TargetRightAscension The supplied value is out of range for this property. -21:08:26.395 TargetRightAscension Write OK Legal value 06:34:15.00 HH:MM:SS written successfully -21:08:26.435 TargetDeclination Write ERROR Unexpected DriverAccessCOMException, : Property TargetDeclination The supplied value is out of range for this property. -21:08:26.454 TargetDeclination Write ERROR Unexpected DriverAccessCOMException, : Property TargetDeclination The supplied value is out of range for this property. -21:08:26.506 TargetDeclination Write OK Legal value 01:00:00.00 DD:MM:SS written successfully -21:08:57.865 SlewToTarget INFO Slewed within 45.0 arc seconds of expected RA: 07:34:15.00, actual RA: 07:34:12.00 -21:08:57.880 SlewToTarget OK Slewed OK. DEC: 03:00:00.00 -21:08:57.897 SlewToTarget OK The TargetRightAscension property 07:34:15.00 matches the expected RA OK. -21:08:57.915 SlewToTarget OK The TargetDeclination property 03:00:00.00 matches the expected Declination OK. -21:08:57.959 SlewToTarget (Bad L) ERROR Unexpected DriverAccessCOMException, Exception setting bad RA coordinate: Property TargetRightAscension The supplied value is out of range for this property. -21:08:58.003 SlewToTarget (Bad L) ERROR Unexpected DriverAccessCOMException, Exception setting bad Dec coordinate: Property TargetDeclination The supplied value is out of range for this property. -21:08:58.064 SlewToTarget (Bad H) ERROR Unexpected DriverAccessCOMException, Exception setting bad RA coordinate: Property TargetRightAscension The supplied value is out of range for this property. -21:08:58.114 SlewToTarget (Bad H) ERROR Unexpected DriverAccessCOMException, Exception setting bad Dec coordinate: Property TargetDeclination The supplied value is out of range for this property. -21:09:29.948 SlewToTargetAsync INFO Slewed within 45.0 arc seconds of expected RA: 06:34:47.00, actual RA: 06:34:44.00 -21:09:29.964 SlewToTargetAsync OK Slewed OK. DEC: 04:00:00.00 -21:09:29.979 SlewToTargetAsync OK The TargetRightAscension property 06:34:47.00 matches the expected RA OK. -21:09:29.996 SlewToTargetAsync OK The TargetDeclination property 04:00:00.00 matches the expected Declination OK. -21:09:30.038 SlewToTargetAsync (Bad L) ERROR Unexpected DriverAccessCOMException, Exception setting bad RA coordinate: Property TargetRightAscension The supplied value is out of range for this property. -21:09:30.085 SlewToTargetAsync (Bad L) ERROR Unexpected DriverAccessCOMException, Exception setting bad Dec coordinate: Property TargetDeclination The supplied value is out of range for this property. -21:09:30.150 SlewToTargetAsync (Bad H) ERROR Unexpected DriverAccessCOMException, Exception setting bad RA coordinate: Property TargetRightAscension The supplied value is out of range for this property. -21:09:30.196 SlewToTargetAsync (Bad H) ERROR Unexpected DriverAccessCOMException, Exception setting bad Dec coordinate: Property TargetDeclination The supplied value is out of range for this property. -21:09:30.257 DestinationSideOfPier Test skipped as AligmentMode is not German Polar -21:09:30.278 SlewToAltAz ERROR Unexpected DriverAccessCOMException, CanSlewAltAz is True: Wrong tracking state -21:09:30.322 SlewToAltAz (Bad L) ERROR Unexpected DriverAccessCOMException, slewing to bad Altitude coordinate: Wrong tracking state -21:09:30.343 SlewToAltAz (Bad L) ERROR Unexpected DriverAccessCOMException, slewing to bad Azimuth coordinate: Wrong tracking state -21:09:30.407 SlewToAltAz (Bad H) ERROR Unexpected DriverAccessCOMException, slewing to bad Altitude coordinate: Wrong tracking state -21:09:30.426 SlewToAltAz (Bad H) ERROR Unexpected DriverAccessCOMException, slewing to bad Azimuth coordinate: Wrong tracking state -21:09:30.492 SlewToAltAzAsync ERROR Unexpected DriverAccessCOMException, CanSlewAltAzAsync is True: Wrong tracking state -21:09:30.534 SlewToAltAzAsync (Bad L) ERROR Unexpected DriverAccessCOMException, slewing to bad Altitude coordinate: Wrong tracking state -21:09:30.552 SlewToAltAzAsync (Bad L) ERROR Unexpected DriverAccessCOMException, slewing to bad Azimuth coordinate: Wrong tracking state -21:09:30.617 SlewToAltAzAsync (Bad H) ERROR Unexpected DriverAccessCOMException, slewing to bad Altitude coordinate: Wrong tracking state -21:09:30.637 SlewToAltAzAsync (Bad H) ERROR Unexpected DriverAccessCOMException, slewing to bad Azimuth coordinate: Wrong tracking state -21:10:05.974 SyncToTarget OK Slewed to start position OK. RA: 07:35:20.00 -21:10:05.993 SyncToTarget OK Slewed to start position OK. DEC: 26:55:00.00 -21:10:06.721 SyncToTarget OK Synced to sync position OK. RA: 07:31:20.00 -21:10:06.736 SyncToTarget OK Synced to sync position OK. DEC: 25:55:00.00 -21:10:37.626 SyncToTarget OK Slewed back to start position OK. RA: 07:35:20.00 -21:10:37.641 SyncToTarget OK Slewed back to start position OK. DEC: 26:55:00.00 -21:10:38.363 SyncToTarget OK Synced to reversed sync position OK. RA: 07:39:20.00 -21:10:38.381 SyncToTarget OK Synced to reversed sync position OK. DEC: 27:55:00.00 -21:11:09.639 SyncToTarget OK Slewed back to start position OK. RA: 07:35:20.00 -21:11:09.657 SyncToTarget OK Slewed back to start position OK. DEC: 26:55:00.00 -21:11:09.697 SyncToTarget (Bad L) ERROR Unexpected DriverAccessCOMException, Exception setting bad RA coordinate: Property TargetRightAscension The supplied value is out of range for this property. -21:11:09.741 SyncToTarget (Bad L) ERROR Unexpected DriverAccessCOMException, Exception setting bad Dec coordinate: Property TargetDeclination The supplied value is out of range for this property. -21:11:09.803 SyncToTarget (Bad H) ERROR Unexpected DriverAccessCOMException, Exception setting bad RA coordinate: Property TargetRightAscension The supplied value is out of range for this property. -21:11:09.847 SyncToTarget (Bad H) ERROR Unexpected DriverAccessCOMException, Exception setting bad Dec coordinate: Property TargetDeclination The supplied value is out of range for this property. -21:11:10.010 SyncToAltAz OK CanSyncAltAz is False and a MethodNotImplementedException exception was generated as expected - -SideOfPier Model Tests -21:11:10.080 SideOfPier Model Tests INFO Tests skipped because this driver does Not support SideOfPier Read - -Post-run Checks -21:11:10.180 Mount Safety INFO Tracking can't be turned off for this mount, please switch off manually. - -Conformance test complete - -Your driver had 53 errors, 0 warnings and 0 issues \ No newline at end of file diff --git a/TestConsole/Program.cs b/TelescopeTestConsole/Program.cs similarity index 71% rename from TestConsole/Program.cs rename to TelescopeTestConsole/Program.cs index 6c8fa71..72b790c 100644 --- a/TestConsole/Program.cs +++ b/TelescopeTestConsole/Program.cs @@ -22,17 +22,19 @@ namespace ASCOM // Uncomment the code that's required #if UseChooser // choose the device - string id = ASCOM.DriverAccess.Telescope.Choose("ASCOM.MeadeAutostar497.Telescope"); + string id = ASCOM.DriverAccess.Telescope.Choose("ASCOM.Meade.net.Telescope"); if (string.IsNullOrEmpty(id)) return; // create this device ASCOM.DriverAccess.Telescope device = new ASCOM.DriverAccess.Telescope(id); #else // this can be replaced by this code, it avoids the chooser and creates the driver class directly. - ASCOM.DriverAccess.Telescope device = new ASCOM.DriverAccess.Telescope("ASCOM.MeadeAutostar497.Telescope"); + ASCOM.DriverAccess.Telescope device = new ASCOM.DriverAccess.Telescope("ASCOM.Meade.net.Telescope"); #endif // now run some tests, adding code to your driver so that the tests will pass. // these first tests are common to all drivers. + + Console.WriteLine("name " + device.Name); Console.WriteLine("description " + device.Description); Console.WriteLine("DriverInfo " + device.DriverInfo); @@ -41,29 +43,11 @@ namespace ASCOM // TODO add more code to test the driver. device.Connected = true; - //Console.WriteLine(device.Slewing); + //Console.WriteLine($"Altitute {device.Altitude}"); - //device.UTCDate = DateTime.UtcNow; - //Console.WriteLine(device.UTCDate.ToLocalTime()); + //Console.WriteLine($"Dec {device.Declination}"); - - //Console.WriteLine(device.AlignmentMode); - - - - //double l = device.SiteLatitude; - //device.SiteLatitude = l; - - //double l = device.SiteLongitude; - //device.SiteLongitude = l; - //Console.WriteLine(device.SiteLongitude); - - //Console.WriteLine(device.RightAscension); - - //device.SlewToAltAz(0,0); - - - Console.WriteLine(device.TrackingRate); + device.SlewToAltAz(30, 45); device.Connected = false; Console.WriteLine("Press Enter to finish"); diff --git a/TestConsole/Properties/AssemblyInfo.cs b/TelescopeTestConsole/Properties/AssemblyInfo.cs similarity index 93% rename from TestConsole/Properties/AssemblyInfo.cs rename to TelescopeTestConsole/Properties/AssemblyInfo.cs index 86599a1..2b89b32 100644 --- a/TestConsole/Properties/AssemblyInfo.cs +++ b/TelescopeTestConsole/Properties/AssemblyInfo.cs @@ -5,11 +5,11 @@ using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("q Test Application")] +[assembly: AssemblyTitle("Meade.net Test Application")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("ASCOM Initiative")] -[assembly: AssemblyProduct("q")] +[assembly: AssemblyProduct("Meade.net")] [assembly: AssemblyCopyright("Copyright © ASCOM Initiative 2014")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/TestConsole/TestConsole.csproj b/TelescopeTestConsole/TelescopeTestConsole.csproj similarity index 92% rename from TestConsole/TestConsole.csproj rename to TelescopeTestConsole/TelescopeTestConsole.csproj index 34ad58e..deb5415 100644 --- a/TestConsole/TestConsole.csproj +++ b/TelescopeTestConsole/TelescopeTestConsole.csproj @@ -8,15 +8,15 @@ {D5207217-61C7-4E94-8097-91DBACE57D2A} Exe Properties - TestConsole - TestConsole - v4.6 + ASCOM.Meade.net + ASCOM.Meade.net.Test + v4.7.1 512 - AnyCPU + x86 true full false diff --git a/TelescopeTestConsole/app.config b/TelescopeTestConsole/app.config new file mode 100644 index 0000000..70dcdba --- /dev/null +++ b/TelescopeTestConsole/app.config @@ -0,0 +1,3 @@ + + +