#define Focuser using System; using System.Diagnostics; using System.Runtime.InteropServices; using ASCOM.Utilities; using ASCOM.DeviceInterface; using System.Globalization; using System.Collections; using System.Reflection; using ASCOM.Meade.net.Wrapper; 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 comPort; // Variables to hold the currrent device configuration /// /// Private variable to hold an ASCOM Utilities object /// private readonly IUtil _utilities; /// /// Variable to hold the trace logger object (creates a diagnostic log file with information that you specify) /// internal static TraceLogger tl; private readonly ISharedResourcesWrapper _sharedResourcesWrapper; /// /// Initializes a new instance of the class. /// Must be public for COM registration. /// public Focuser() { //todo move this out to IOC _utilities = new Util(); //Initialise util object _sharedResourcesWrapper = new SharedResourcesWrapper(); tl = new TraceLogger("", "Meade.net.focusser"); tl.LogMessage("Focuser", "Starting initialisation"); ReadProfile(); // Read device configuration from the ASCOM Profile store IsConnected = false; // Initialise connected to false 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() { tl.LogMessage("SetupDialog", "Opening setup dialog"); _sharedResourcesWrapper.SetupDialog(); ReadProfile(); tl.LogMessage("SetupDialog", "complete"); } 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); _sharedResourcesWrapper.SendBlind(command); // or //throw new ASCOM.MethodNotImplementedException("CommandBlind"); // DO NOT have both these sections! One or the other } public bool CommandBool(string command, bool raw) { CheckConnected("CommandBool"); //string ret = CommandString(command, raw); // TODO decode the return string and return true or false // or throw new 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 _sharedResourcesWrapper.SendString(command); throw new ASCOM.MethodNotImplementedException("CommandString"); } public void Dispose() { // Clean up the tracelogger and util objects tl.Enabled = false; tl.Dispose(); tl = null; } public bool Connected { get { LogMessage("Connected", "Get {0}", IsConnected); return IsConnected; } set { tl.LogMessage("Connected", "Set {0}", value); if (value == IsConnected) return; if (value) { try { _sharedResourcesWrapper.Connect("Serial"); try { SelectSite(1); SetLongFormat(true); IsConnected = true; } catch (Exception) { _sharedResourcesWrapper.Disconnect("Serial"); throw; } } catch (Exception ex) { LogMessage("Connected Set", "Error connecting to port {0} - {1}", comPort, ex.Message); } } else { LogMessage("Connected Set", "Disconnecting from port {0}", comPort); _sharedResourcesWrapper.Disconnect("Serial"); IsConnected = false; } } } private void SetLongFormat(bool setLongFormat) { _sharedResourcesWrapper.Lock(() => { var result = _sharedResourcesWrapper.SendString(":GZ#"); //:GZ# Get telescope azimuth //Returns: DDD*MM#T or DDD*MM’SS# //The current telescope Azimuth depending on the selected precision. bool isLongFormat = result.Length > 6; if (isLongFormat != setLongFormat) { _utilities.WaitForMilliseconds(500); _sharedResourcesWrapper.SendBlind(":U#"); //:U# Toggle between low/hi precision positions //Low - RA displays and messages HH:MM.T sDD*MM //High - Dec / Az / El displays and messages HH:MM: SS sDD*MM:SS // Returns Nothing } }); } private void SelectSite(int site) { _sharedResourcesWrapper.SendBlind($":W{site}#"); //:W# //Set current site to, an ASCII digit in the range 1..4 //Returns: Nothing } 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"); CheckConnected("Halt"); //A single halt command is sometimes missed by the #909 apm, so let's do it a few times to be safe. Stopwatch stopwatch = Stopwatch.StartNew(); while (stopwatch.ElapsedMilliseconds < 1000) { _sharedResourcesWrapper.SendBlind(":FQ#"); //:FQ# Halt Focuser Motion //Returns: Nothing _utilities.WaitForMilliseconds(250); } } 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()); CheckConnected("Move"); //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) { _sharedResourcesWrapper.Lock(() => { //_sharedResourcesWrapper.SendBlind(":FF#"); //:FF# Set Focus speed to fastest setting //Returns: Nothing //:FS# Set Focus speed to slowest setting //Returns: Nothing //:F# Autostar, Autostar II – set focuser speed to where is an ASCII digit 1..4 //Returns: Nothing //All others – Not Supported _utilities.WaitForMilliseconds(100); //A Single focus command sometimes gets lost on the #909, so sending lots of them solves the issue. Stopwatch stopwatch = Stopwatch.StartNew(); while (stopwatch.ElapsedMilliseconds < steps) { _sharedResourcesWrapper.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(250); } Halt(); //This gives the focuser time to physically stop. _utilities.WaitForMilliseconds(1000); }); } 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; set; } /// /// Use this function to throw an exception if we aren't connected to the hardware /// /// private void CheckConnected(string message) { if (!IsConnected) { throw new ASCOM.NotConnectedException(message); } } /// /// Read the device configuration from the ASCOM Profile store /// internal void ReadProfile() { var profileProperties = _sharedResourcesWrapper.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 } }