1
0
mirror of https://bitbucket.org/cjdskunkworks/lynxastrodewcontroller.git synced 2026-05-03 09:18:51 +00:00
Files
lynxastrodewcontroller/LynxAstro.DewController.Switch/Switch.cs
T
2021-02-22 12:07:43 +00:00

608 lines
22 KiB
C#

#define Switch
using System;
using System.Runtime.InteropServices;
using ASCOM.Utilities;
using ASCOM.DeviceInterface;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
namespace ASCOM.LynxAstro.DewController
{
//
// Your driver's DeviceID is ASCOM.LynxAstro.DewController.Switch
//
// The Guid attribute sets the CLSID for ASCOM.LynxAstro.DewController.Switch
// The ClassInterface/None attribute prevents an empty interface called
// _LynxAstro.DewController 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.
//
/// <summary>
/// ASCOM Switch Driver for LynxAstro.DewController.
/// </summary>
[Guid("3a29744a-f33f-4843-a7e0-6938d9bd50ba")]
[ProgId("ASCOM.LynxAstro.DewController.Switch")]
[ServedClassName("LynxAstro.DewController")]
[ClassInterface(ClassInterfaceType.None)]
public class Switch : AscomDriverBase, ISwitchV2
{
/// <summary>
/// ASCOM DeviceID (COM ProgID) for this driver.
/// The DeviceID is used by ASCOM applications to load the driver at runtime.
/// </summary>
internal static string DriverId = Marshal.GenerateProgIdForType(MethodBase.GetCurrentMethod().DeclaringType ?? throw new System.InvalidOperationException());
/// <summary>
/// Initializes a new instance of the <see cref="LynxAstro.DewController"/> class.
/// Must be public for COM registration.
/// </summary>
/// <param name="tl"></param>
public Switch()
{
Initialise(nameof(Switch));
}
public Switch(ISharedResourcesWrapper sharedResourcesWrapper) : base (sharedResourcesWrapper)
{
Initialise(nameof(Switch));
}
//
// PUBLIC COM INTERFACE ISwitchV2 IMPLEMENTATION
//
#region Common properties and methods.
/// <summary>
/// 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!
/// </summary>
public void SetupDialog()
{
LogMessage("SetupDialog", "Opening setup dialog");
SharedResourcesWrapper.SetupDialog();
ReadProfile();
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");
SharedResourcesWrapper.SendBlind(command);
}
public bool CommandBool(string command, bool raw)
{
CheckConnected("CommandBool");
throw new ASCOM.MethodNotImplementedException("CommandBool");
}
public string CommandString(string command, bool raw)
{
CheckConnected("CommandString");
return SharedResourcesWrapper.SendString(command);
}
public void Dispose()
{
// Clean up the trace logger and util objects
Tl.Enabled = false;
Tl.Dispose();
Tl = null;
}
private string DecodeResult(string pattern, string encodedString)
{
var decodedString = encodedString.Substring(pattern.Length).TrimEnd('#');
return decodedString;
}
public bool Connected
{
get
{
LogMessage("Connected", "Get {0}", IsConnected);
return IsConnected;
}
set
{
LogMessage("Connected", "Set {0}", value);
if (value == IsConnected)
return;
if (value)
{
try
{
ReadProfile();
LogMessage("Connected Set", "Connecting to port {0}", ComPort);
var connectionInfo = SharedResourcesWrapper.Connect("Serial", DriverId, Tl);
try
{
LogMessage("Connected Set", $"Connected to port {ComPort}. Version:{SharedResourcesWrapper.FirmwareVersion}");
IsConnected = true;
LogMessage("Connected Set", $"Connected OK.");
var maxSwitch = MaxSwitch;
}
catch (Exception)
{
SharedResourcesWrapper.Disconnect("Serial", DriverId);
throw;
}
}
catch (Exception ex)
{
LogMessage("Connected Set", "Error connecting to port {0} - {1}", ComPort, ex.Message);
}
}
else
{
LogMessage("Connected Set", "Disconnecting from port {0}", ComPort);
SharedResourcesWrapper.Disconnect("Serial", DriverId);
IsConnected = false;
}
}
}
public short InterfaceVersion
{
// set by the driver wizard
get
{
LogMessage("InterfaceVersion Get", "2");
return Convert.ToInt16("2");
}
}
public string Name
{
get
{
string name = "LynxAstro.DewController";
Tl.LogMessage("Name Get", name);
return name;
}
}
#endregion
#region ISwitchV2 Implementation
private short numSwitch = 0;
private List<string> switchNames = new List<string>();
/// <summary>
/// The number of switches managed by this driver
/// </summary>
public short MaxSwitch
{
get
{
CheckConnected("MaxSwitch Get");
var result = SharedResourcesWrapper.SendString(":GD#");
var decoded = DecodeResult(":GD", result);
this.numSwitch = short.Parse(decoded);
//Command: :GD#
//Purpose: Get the device type, i.e.the number of channels the dew controller has.
//Response: :GDX# where X is either 1 or 4 depending on the number of channels this device has
Tl.LogMessage("MaxSwitch Get", numSwitch.ToString());
return this.numSwitch;
}
}
/// <summary>
/// Return the name of switch n
/// </summary>
/// <param name="id">The switch number to return</param>
/// <returns>
/// The name of the switch
/// </returns>
public string GetSwitchName(short id)
{
Validate("GetSwitchName", id);
ReadProfile();
return switchNames.Count > id ? switchNames[id] : string.Empty;
}
/// <summary>
/// Sets a switch name to a specified value
/// </summary>
/// <param name="id">The number of the switch whose name is to be set</param>
/// <param name="name">The name of the switch</param>
public void SetSwitchName(short id, string name)
{
Validate("SetSwitchName", id);
ReadProfile();
while (id > switchNames.Count)
switchNames.Add(string.Empty);
switchNames[id] = name;
WriteSwitchNames();
}
/// <summary>
/// Gets the switch description.
/// </summary>
/// <param name="id">The id.</param>
/// <returns></returns>
public string GetSwitchDescription(short id)
{
Validate("GetSwitchDescription", id);
return "Control Knob";
}
/// <summary>
/// Reports if the specified switch can be written to.
/// This is false if the switch cannot be written to, for example a limit switch or a sensor.
/// The default is true.
/// </summary>
/// <param name="id">The number of the switch whose write state is to be returned</param><returns>
/// <c>true</c> if the switch can be written to, otherwise <c>false</c>.
/// </returns>
/// <exception cref="MethodNotImplementedException">If the method is not implemented</exception>
/// <exception cref="InvalidValueException">If id is outside the range 0 to MaxSwitch - 1</exception>
public bool CanWrite(short id)
{
Validate("CanWrite", id);
// default behavour is to report true
Tl.LogMessage("CanWrite", string.Format("CanWrite({0}) - default true", id));
return true;
// implementation should report the correct behaviour
//tl.LogMessage("CanWrite", string.Format("CanWrite({0}) - not implemented", id));
//throw new MethodNotImplementedException("CanWrite");
}
#region boolean switch members
/// <summary>
/// Return the state of switch n
/// a multi-value switch must throw a not implemented exception
/// </summary>
/// <param name="id">The switch number to return</param>
/// <returns>
/// True or false
/// </returns>
public bool GetSwitch(short id)
{
var value = GetSwitchValue(id);
return value > 0;
}
/// <summary>
/// Sets a switch to the specified state
/// If the switch cannot be set then throws a MethodNotImplementedException.
/// A multi-value switch must throw a not implemented exception
/// setting it to false will set it to its minimum value.
/// </summary>
/// <param name="id"></param>
/// <param name="state"></param>
public void SetSwitch(short id, bool state)
{
Validate("SetSwitch", id);
if (!CanWrite(id))
{
var str = string.Format("SetSwitch({0}) - Cannot Write", id);
Tl.LogMessage("SetSwitch", str);
throw new MethodNotImplementedException(str);
}
SetSwitchValue(id, state ? 1023 : 0);
}
#endregion
#region analogue members
/// <summary>
/// returns the maximum value for this switch
/// boolean switches must return 1.0
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public double MaxSwitchValue(short id)
{
Validate("MaxSwitchValue", id);
// boolean switch implementation:
return 1023;
// or
//tl.LogMessage("MaxSwitchValue", string.Format("MaxSwitchValue({0}) - not implemented", id));
//throw new MethodNotImplementedException("MaxSwitchValue");
}
/// <summary>
/// returns the minimum value for this switch
/// boolean switches must return 0.0
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public double MinSwitchValue(short id)
{
Validate("MinSwitchValue", id);
// boolean switch implementation:
return 0;
// or
//tl.LogMessage("MinSwitchValue", string.Format("MinSwitchValue({0}) - not implemented", id));
//throw new MethodNotImplementedException("MinSwitchValue");
}
/// <summary>
/// returns the step size that this switch supports. This gives the difference between
/// successive values of the switch.
/// The number of values is ((MaxSwitchValue - MinSwitchValue) / SwitchStep) + 1
/// boolean switches must return 1.0, giving two states.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public double SwitchStep(short id)
{
Validate("SwitchStep", id);
// boolean switch implementation:
return 1;
// or
//tl.LogMessage("SwitchStep", string.Format("SwitchStep({0}) - not implemented", id));
//throw new MethodNotImplementedException("SwitchStep");
}
/// <summary>
/// returns the analogue switch value for switch id
/// boolean switches must throw a not implemented exception
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public double GetSwitchValue(short id)
{
Validate("GetSwitchValue", id);
Tl.LogMessage("GetSwitchValue", string.Format("GetSwitchValue({0}) - not implemented", id));
var channel = ChannelToLetter(id);
var encoded = SharedResourcesWrapper.SendString($":GC{channel}#");
var decoded = DecodeResult(":GC", encoded);
return double.Parse(decoded.TrimStart(channel));
//Command: :GCX# where X is the channel A, B, C or D to retrieve.
//Purpose: Get the current power setting for a specific channel.
//Response: :GCXVVVV# where X is the channel A, B, C or D returned and VVVV is the power level between 0 - 1023.
//Possible Errors
//:ER5# = Channel out of range, e.g. channel B on a 1 channel device.
}
private char ChannelToLetter(short id)
{
UInt16 ord = (UInt16) 'A';
ord += (ushort)id;
return (char)ord;
}
/// <summary>
/// set the analogue value for this switch.
/// If the switch cannot be set then throws a MethodNotImplementedException.
/// If the value is not between the maximum and minimum then throws an InvalidValueException
/// boolean switches must throw a not implemented exception.
/// </summary>
/// <param name="id"></param>
/// <param name="value"></param>
public void SetSwitchValue(short id, double value)
{
Validate("SetSwitchValue", id, value);
if (!CanWrite(id))
{
Tl.LogMessage("SetSwitchValue", string.Format("SetSwitchValue({0}) - Cannot write", id));
throw new ASCOM.MethodNotImplementedException(string.Format("SetSwitchValue({0}) - Cannot write", id));
}
//Tl.LogMessage("SetSwitchValue", string.Format("SetSwitchValue({0}) = {1} - not implemented", id, value));
//throw new MethodNotImplementedException("SetSwitchValue");
//Command: :SCXVVVV# where X is the channel A, B, C or D to set and VVVV is the power level
//between 0 - 1023.The power level must be 4 digits long so pad with leading zeros if necessary.
//Purpose: Set the current power setting for a specific channel.
//Response: :SC1# indicates success. Run a GA or GC command to verify.
//Possible Errors
//:ER4# = Not enough data received - make sure you zero pad the power level.
//:ER5# = Channel or power level out of range, e.g. channel B on a 1 channel device or power above 1023.
var channel = ChannelToLetter(id);
var encoded = SharedResourcesWrapper.SendString($":SC{channel}{value:0000}#");
var decoded = DecodeResult(":SC", encoded);
if (decoded != "1")
{
throw new InvalidValueException($"Unable to set switch {{id}} to value {value}");
}
//return double.Parse(decoded.TrimStart(channel));
}
#endregion
#endregion
#region private methods
/// <summary>
/// Checks that the switch id is in range and throws an InvalidValueException if it isn't
/// </summary>
/// <param name="message">The message.</param>
/// <param name="id">The id.</param>
private void Validate(string message, short id)
{
if (id < 0 || id >= numSwitch)
{
Tl.LogMessage(message, string.Format("Switch {0} not available, range is 0 to {1}", id, numSwitch - 1));
throw new InvalidValueException(message, id.ToString(), string.Format("0 to {0}", numSwitch - 1));
}
}
/// <summary>
/// Checks that the switch id and value are in range and throws an
/// InvalidValueException if they are not.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="id">The id.</param>
/// <param name="value">The value.</param>
private void Validate(string message, short id, double value)
{
Validate(message, id);
var min = MinSwitchValue(id);
var max = MaxSwitchValue(id);
if (value < min || value > max)
{
Tl.LogMessage(message, string.Format("Value {1} for Switch {0} is out of the allowed range {2} to {3}", id, value, min, max));
throw new InvalidValueException(message, value.ToString(), string.Format("Switch({0}) range {1} to {2}", id, min, max));
}
}
/// <summary>
/// Checks that the number of states for the switch is correct and throws a methodNotImplemented exception if not.
/// Boolean switches must have 2 states and multi-value switches more than 2.
/// </summary>
/// <param name="message"></param>
/// <param name="id"></param>
/// <param name="expectBoolean"></param>
//private void Validate(string message, short id, bool expectBoolean)
//{
// Validate(message, id);
// var ns = (int)(((MaxSwitchValue(id) - MinSwitchValue(id)) / SwitchStep(id)) + 1);
// if ((expectBoolean && ns != 2) || (!expectBoolean && ns <= 2))
// {
// tl.LogMessage(message, string.Format("Switch {0} has the wriong number of states", id, ns));
// throw new MethodNotImplementedException(string.Format("{0}({1})", message, id));
// }
//}
#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.
//
/// <summary>
/// Register or unregister the driver with the ASCOM Platform.
/// This is harmless if the driver is already registered/unregistered.
/// </summary>
/// <param name="bRegister">If <c>true</c>, registers the driver, otherwise unregisters it.</param>
private static void RegUnregASCOM(bool bRegister)
{
using (var P = new ASCOM.Utilities.Profile())
{
P.DeviceType = "Switch";
if (bRegister)
{
P.Register(DriverId, DriverDescription);
}
else
{
P.Unregister(DriverId);
}
}
}
/// <summary>
/// This function registers the driver with the ASCOM Chooser and
/// is called automatically whenever this class is registered for COM Interop.
/// </summary>
/// <param name="t">Type of the class being registered, not used.</param>
/// <remarks>
/// This method typically runs in two distinct situations:
/// <list type="numbered">
/// <item>
/// In Visual Studio, when the project is successfully built.
/// For this to work correctly, the option <c>Register for COM Interop</c>
/// must be enabled in the project settings.
/// </item>
/// <item>During setup, when the installer registers the assembly for COM Interop.</item>
/// </list>
/// This technique should mean that it is never necessary to manually register a driver with ASCOM.
/// </remarks>
[ComRegisterFunction]
public static void RegisterASCOM(Type t)
{
RegUnregASCOM(true);
}
/// <summary>
/// This function unregisters the driver from the ASCOM Chooser and
/// is called automatically whenever this class is unregistered from COM Interop.
/// </summary>
/// <param name="t">Type of the class being registered, not used.</param>
/// <remarks>
/// This method typically runs in two distinct situations:
/// <list type="numbered">
/// <item>
/// In Visual Studio, when the project is cleaned or prior to rebuilding.
/// For this to work correctly, the option <c>Register for COM Interop</c>
/// must be enabled in the project settings.
/// </item>
/// <item>During uninstall, when the installer unregisters the assembly from COM Interop.</item>
/// </list>
/// This technique should mean that it is never necessary to manually unregister a driver from ASCOM.
/// </remarks>
[ComUnregisterFunction]
public static void UnregisterASCOM(Type t)
{
RegUnregASCOM(false);
}
#endregion
/// <summary>
/// Use this function to throw an exception if we aren't connected to the hardware
/// </summary>
/// <param name="message"></param>
private void CheckConnected(string message)
{
if (!IsConnected)
{
throw new ASCOM.NotConnectedException(message);
}
}
protected void WriteSwitchNames()
{
var profileProperties = SharedResourcesWrapper.ReadProfile();
profileProperties.SwitchNames.Clear();
profileProperties.SwitchNames.AddRange(switchNames);
SharedResourcesWrapper.WriteProfile(profileProperties);
}
#endregion
}
}