Introduction To Developing ASCOM Drivers
Introduction To Developing ASCOM Drivers
Introduction To Developing ASCOM Drivers
Introduction
ASCOM driver development is not terribly difficult but it isn't the simplest way to start C# or VB.NET development. There seem to be a lot of people who are starting this as almost their first .NET based software development project. These notes are an attempt to help.
This is the sort of thing you will get. Click on OK and your project will be built. You will get a project and will be shown a help file. The first instructions are about modifying the project name and the default namespace; these things should now be done by the template so you do not need to make any changes. The project properties should look like this:
Click on the Start Debugging button or press F5. You will probably get your first error message:
The message says that the assembly cannot be registered; this is because, on W7 or Vista access to some parts of the registry are restricted to administrators. The good news is that this is only done after the driver has been built successfully. If you're using XP you won't see this. Close down Visual Studio, saving your project, and restart it as administrator. The simplest way to do this is bring up the context menu on the short cut to Visual Studio and select Run As Administrator. Select your project in the recent projects and build again. Now you get a different error
message:
This means that your driver has been successfully built and registered but it cannot be run because it's a dll and it needs an executable project. The intent was to run a script at this point but that doesn't work in the Express editions. So you need to create an executable project: In the Solution Explorer, select the Solution and use the context menu to select Add - New Project... Select Visual C# and Console Application and choose a project name:
Click on OK and your new project will be created in this example it's called FocuserTest. Use the Context menu to Add References to the ASCOM Client Toolkit and ASCOM Device Interfaces. Make sure the Version 6.0.0.0 versions are selected: Check the project properties and make sure that your test project uses the save version of .NET as the driver, probably .NET 3.5. This is needed to be able to debug into the driver from the test program.
Use the context menu to set FocuserTest to be the Startup Project. Add this line to the Main subroutine in Program.cs:
string id = ASCOM.DriverAccess.Focuser.Choose("");
There you are! Your driver is working! It isn't doing much yet but it's a start.
Notes: If you aren't making a focuser select the driver type you are using everywhere that we have mentioned a focuser. I've skimmed a number of basic development concepts, if you aren't familiar with these search on the web. Typing your question into a search engine should come up with something useful. Good ways to get help are to highlight the error and press F1 or use the context menu and select Show Error help. Another useful way is to search using the error message. It's worth getting several opinions though. No amount of advice or tutorials can be a substitute for thought. Experiment and expect to throw a lot of code away.
Move to the SetupDialogForm code and add code to the SetupDialogForm constructor to fill the combo box with the Com port names:
comboBoxComPort.Items.Clear(); using (ASCOM.Utilities.Serial serial = new Utilities.Serial()) { foreach (var item in serial.AvailableCOMPorts) { comboBoxComPort.Items.Add(item); } }
Make the driverId constant internal instead of private. Save the com port name in the CmdOkClick event handler:
private void CmdOkClick(object sender, EventArgs e) { using (ASCOM.Utilities.Profile p = new Utilities.Profile()) { p.DeviceType = "Focuser"; p.WriteValue(Focuser.driverId, "ComPort", (string)comboBoxComPort.SelectedItem); } Dispose(); } That's enough to be able to select and save the com port, it would be nice to set the current com port when you load the setup dialog but I'll leave that for now.
Setting up the driver Start by going through the public properties setting the ones that aren't implemented so they throw
} } else { // disconnect if (serialPort != null && serialPort.Connected) serialPort.Connected = false; } } } Additional things would be to send a command to the hardware to check that it's there. It's now possible to extend the focuser test application to check a few things: static void Main(string[] args) { string id = ASCOM.DriverAccess.Focuser.Choose(""); ASCOM.DriverAccess.Focuser focuser = new ASCOM.DriverAccess.Focuser(id); Console.WriteLine("name " + focuser.Name); Console.WriteLine("description " + focuser.Description); Console.WriteLine("DriverInfo " + focuser.DriverInfo); Console.WriteLine("driverVersion " + focuser.DriverVersion); Console.WriteLine("Press Enter to finish"); Console.ReadLine();
Add a few more things: Console.WriteLine("Absolute Focuser " + focuser.Absolute); Console.WriteLine("MaxStep " + focuser.MaxStep); Console.WriteLine("MaxIncrement " + focuser.MaxIncrement); Console.WriteLine("Connected " + focuser.Connected);
Keep adding tests as you add more functionality: focuser.Connected = true; Console.WriteLine("Connected " + focuser.Connected); If you get errors set breakpoints and debug to see what's happening.
You may find that you can't debug from the test program to the driver, the simplest way is to set breakpoints in the driver a run to those. It's important to make sure that both the driver and the test application are using the same version of .NET, if they aren't you can't debug into the driver. Now to send some commands: A good way to implement sending commands is to put the command in the CommandString method: public string CommandString(string command, bool raw) { if (!this.Connected) throw new ASCOM.NotConnectedException(); serialPort.ClearBuffers(); serialPort.Transmit(command); return serialPort.ReceiveTerminated("#"); } This assumes that every command is terminated with a '#' character AND that this chanacter cannot appear in a message. You may need to add message terminators to the command. Every method that sends a command calls CommandString, for example: public int Position { get { // position command is P#, returns nnnn# string ret = CommandString("P#", true); return int.Parse(ret); } } One thing that trips people up is that Visual Studio re-registers the driver every time it's compiled so you need to run the setup dialog to select the COM port each time, otherwise you get an error in connected because the com port name is not set. I can't run this fully because I don't have hardware that implements this protocol. Obviously a real driver would need to implement a real protocol. There are a number of things missing: Very little error checking. No checks on the validity of data. Here's the complete driver: using using using using using using System; System.Collections; System.Globalization; System.Runtime.InteropServices; ASCOM.DeviceInterface; ASCOM.Utilities;
namespace ASCOM.AcmeAstro { // // Your driver's ID is ASCOM.AcmeAstro.Focuser // // The Guid attribute sets the CLSID for ASCOM.AcmeAstro.Focuser // The ClassInterface/None addribute prevents an empty interface called // _Focuser from being created and used as the [default] interface // [Guid("d762e5e0-f093-4852-b081-8e0206192c45")] [ClassInterface(ClassInterfaceType.None)] [ComVisible(true)] public class Focuser : IFocuserV2 { #region Constants
// // Driver ID and descriptive string that shows in the Chooser // internal const string driverId = "ASCOM.AcmeAstro.Focuser"; // TODO Change the descriptive string for your driver then remove this line private const string driverDescription = "AcmeAstro Focuser"; private Serial serialPort; #endregion #region ASCOM Registration // // Register or unregister driver for ASCOM. This is harmless if already // registered or unregistered. // private static void RegUnregASCOM(bool bRegister) { using (var p = new Profile()) { p.DeviceType = "Focuser"; if (bRegister) p.Register(driverId, driverDescription); else p.Unregister(driverId); } } [ComRegisterFunction] public static void RegisterASCOM(Type t) { RegUnregASCOM(true); } [ComUnregisterFunction] public static void UnregisterASCOM(Type t) { RegUnregASCOM(false); } #endregion #region Implementation of IFocuserV2 public void SetupDialog() { using (var f = new SetupDialogForm()) { f.ShowDialog(); } } public string Action(string actionName, string actionParameters) { throw new ASCOM.MethodNotImplementedException("Action"); } public void CommandBlind(string command, bool raw) { throw new ASCOM.MethodNotImplementedException("CommandBlind"); } public bool CommandBool(string command, bool raw) { throw new ASCOM.MethodNotImplementedException("CommandBool"); } public string CommandString(string command, bool raw)
} public void Dispose() { throw new System.NotImplementedException(); } public void Halt() { // halt command is H# CommandString("H#", false); //throw new System.NotImplementedException(); } public void Move(int value) { // move command is Mnnnn# where nnn is the target position CommandString(string.Format("M{0}#", value), false); //throw new System.NotImplementedException(); } public bool Connected { get { if (serialPort == null) return false; return serialPort.Connected; } set { if (value) { // check if we are connected, return if we are if (serialPort != null && serialPort.Connected) return; // get the port name from the profile string portName; using (ASCOM.Utilities.Profile p = new Profile()) { p.DeviceType = "Focuser"; portName = p.GetValue(driverId, "ComPort"); } if (string.IsNullOrEmpty(portName)) { // report a problem with the port name throw new ASCOM.NotConnectedException("no Com port selected"); } // try to connect using the port try { serialPort = new Serial(); serialPort.PortName = portName; serialPort.Speed = SerialSpeed.ps9600; serialPort.Connected = true; } catch (Exception ex) { // report any error
error", ex);
throw new ASCOM.NotConnectedException("Serial port connection } } else { // disconnect if (serialPort != null && serialPort.Connected) serialPort.Connected = false; }
public string Description { get { return driverDescription; } } public string DriverInfo { get { return driverDescription; } } public string DriverVersion { get { Version version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; return String.Format(CultureInfo.InvariantCulture, "{0}.{1}", version.Major, version.Minor); } } public short InterfaceVersion { get { return 2; } } public string Name { get { return driverDescription; } } public ArrayList SupportedActions { get { return new ArrayList(); } } public bool Absolute { get { return true; } } public bool IsMoving { get { // ismoving command is ?#, returns 1# if moving, 0# if not string ret = CommandString("?#", true); return (ret == "1"); } } // use the V2 connected property
public bool Link { get { return this.Connected; } set { this.Connected = value; } } public int MaxIncrement { get { return 50000; } } public int MaxStep { get { return 50000; } } public int Position { get { // position command is P#, returns nnnn# string ret = CommandString("P#", true); return int.Parse(ret); } } public double StepSize { get { throw new ASCOM.PropertyNotImplementedException("StepSize", false); } } public bool TempComp { get { throw new ASCOM.PropertyNotImplementedException("TempComp", false); } set { throw new ASCOM.PropertyNotImplementedException("tempComp", true); } } public bool TempCompAvailable { get { throw new ASCOM.PropertyNotImplementedException("TempCompAvailable", false); } } public double Temperature { get { throw new ASCOM.PropertyNotImplementedException("Temperature", false); } } } } #endregion