ASCOM LocalServer (singleton) Host |
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:
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.
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.
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.
Build the LocalServer.
Set a reference to the local server project in each of the driver skeleton projects.
In each skeleton driver project:
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.
In project properties, Application tab, change the assembly name to ASCOM.localserverprojectname.drivertype, (e.g., ASCOM.SuperScope.Telescope).
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.
In project properties, Build tab, turn off Register for COM Interop.
Modify the driver class declaration to inherit from
ReferenceCountedObjectBase. Examples:
C#:
public class Telescope :
ReferenceCountedObjectBase,
ITelescope
VB:
Public Class Telescope
'==================================
Inherits ReferenceCountedObjectBase
Implements ITelescope
'==================================
In driver.cs/driver.vb, remove the entire ASCOM Registration region
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.
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
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())
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.
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#.
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.
If you modified the LocalServer, build it again now. This will refresh the stuff that's visible to the drivers.
Build the driver skeletons to verify that you got all of the namespace and other variable changes.
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.
Make sure the drivers are registered through the local server by running it with the /register parameter, see below for details.
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.
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!
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};
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.
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.
|
||
|
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 . |