Driver ASCOM
Michel Llibre - Club d'Astronomie de Quint-Fontsegrives
Ce document présente un exemple d'écriture d'un driver ASCOM en c# à l'aide de Visual Studio et de l'extension ASCOM qui l'on ajoute à Visual Studio par le biais de son menu Extensions/Gérer les extensions/...dans lequel on sélectionnera ASCOM Driver Project Templates. Cette extension est accessible dans Visual Studio lorsque la plateforme ASCOM est installée et ses composants pour les développeurs est installée sur l'ordinateur.
L'exemple traité est un driver pour une roue à filtre. Pour le test du driver cette roue est simulée à l'aide d'une carte Arduino. Les codes du périphérique (code C Arduino), du driver (C# .Net Framework) et d'une application cliente de test (C# .Net Framework) font l'objet de la dernière section de ce document. Ces deux derniers codes utilisent les modèles prédéfinis de l'ASCOM Driver Project Templates. Les autres sections expliquent les modifications qu'il faut apporter à ces modèles pour les adapter à notre exemple, puis comment se fait le déploiement du driver créé.
Ce document est inspiré de :
•.2 tutoriels de http://astro.neutral.org/software/tutorials-ascom-arduino.html qui décrivent la simulation d'une la roue à filtre avec une carte Arduino et l'écriture du driver ASCOM associé, mais en Visual Basic.
•.de la vidéo Youtube https://www.youtube.com/watch?v=rvvGkVRO6wM intitulée "Astronomy Roof Control System - ASCOM.Net Code & Driver - Part 4" qui décrit l'écriture d'un driver ASCOM pour un dôme d'observatoire en C# Visual Studio, à partir des modèles ASCOM.
Sites de téléchargement pour les outils utilisés :
•.Visual Studio : https://visualstudio.microsoft.com/fr/vs/community/
•.Plateforme ASCOM : https://ascom-standards.org/Downloads/Index.htm
•.Développement ASCOM : https://ascom-standards.org/Downloads/PlatDevComponents.htm
Les composants pour développeurs ASCOM incluent une documentation installée localement dans le répertoire C:\Program Files (x86)\ASCOM\Platform 6 Developer Components\Developer Documentation.
Pour tester un driver il faut une application client qui va utiliser le périphérique via le driver.
Nous considérons ici une application cliente du driver ASCOM écrite en c# à l'aide de Visual Studio de type ASCOM Driver Test Forms App. Les généralités suivantes ont pur but essentiel la description de l'activation d'un driver ASCOM pour illustrer les étapes de l'écriture d'une application cliente minimale pour tester un driver.
Dans le code Form.cs de l'application cliente qui veut accéder à un périphérique via le driver ASCOM, on doit, en premier lieu, sélectionner ce driver ce qui se fait dans la callback associée à l'appui de l’utilisateur sur un bouton "Choose" de l'application cliente. Pour cela on utilise la méthode Choose du sdk ASCOM qui est associée au type de périphérique désiré : Cette méthode renvoie le string nom du driver (idname) sélectionné par l'usager, soit par exemple dans le cas d'une roue à filtre (FilterWheel) :
private string idname = ASCOM.DriverAccess.FilterWheel.Choose("");
Cette méthode affichera une liste de choix à l'usager et on pourra stocker son choix dans les settings de l'application :
Properties.Settings.Default.DriverId = idname;
Dans ce cas pour le trouver plus facilement lors d'une autre exécution sans avoir à consulter la liste, on fera plutôt l' appel suivant :
Properties.Settings.Default.DriverId = ASCOM.DriverAccess.FilterWheel.Choose(Properties.Settings.Default.DriverId);
qui proposera le nom choisi lors d'une exécution précédente. C'est ce qui est programmé dans l'appli du Framework ASCOM Driver Test Forms App.
A partir du nom reçu, l'application cliente pourra crée l'objet driver du périphérique dont elle utilisera les méthodes et propriétés :
ASCOM.DriverAccess.FilterWheel myDriver = new ASCOM.DriverAccess.FilterWheel(Properties.Settings.Default.DriverId);
Cette création sera effectuée dans la callback de l'application qui est appelée lorsque l'utilisateur clique sur le bouton "Connect".
Si le couple driver-periph est bien installé, le test suivant sera vrai :
(this.myDriver != null) && (myDriver.Connected == true)
qui est le test généralement programmé dans la méthode get de bool IsConnected.
Ainsi le début de la classe application cliente d'une roue à filtre est le suivant :
public partial class Form1 : Form
{
private ASCOM.DriverAccess.FilterWheel driver;
private int nbFiltres;
public Form1()
{
InitializeComponent();
SetUIState();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (IsConnected) driver.Connected = false;
Properties.Settings.Default.Save();
}
private void buttonChoose_Click(object sender, EventArgs e)
{
Properties.Settings.Default.DriverId = ASCOM.DriverAccess.FilterWheel.Choose(Properties.Settings.Default.DriverId);
SetUIState();
}
private void buttonConnect_Click(object sender, EventArgs e)
{
if (IsConnected) driver.Connected = false;
else
{
driver = new ASCOM.DriverAccess.FilterWheel(Properties.Settings.Default.DriverId);
driver.Connected = true;
}
SetUIState();
}
private void SetUIState()
{
buttonConnect.Enabled = !string.IsNullOrEmpty(Properties.Settings.Default.DriverId);
buttonChoose.Enabled = !IsConnected;
buttonConnect.Text = IsConnected ? "Disconnect" : "Connect";
}
private bool IsConnected
{
get
{return ((this.driver != null) && (driver.Connected == true));}
}
Ensuite il reste à effectuer la programmation des actions que l'on désire faire effectuer à la roue à filtre. Je ne m'étend pas davantage sur cette programmation, car on trouve beaucoup d'exemples de programmation d'applications clientes des drivers ASCOM, mais très peu d'exemples décrivant en détail la programmation du driver lui-même.
Rappelons que l'appel par l'application cliente de la méthode FilterWheel.Choose, produit l'affichage de la popup standard ASCOM suivante :
Le titre de la popup est personnalisé avec le nom du périphérique choisi, ici "FilterWheel".
La liste est éventuellement préremplie par le nom du driver choisi lors d'un appel précédent et passé en argument de FilterWheel.Choose(arg). Si ce n'est pas le bon, on étend la liste et choisit le driver désiré. Il y a intérêt à cliquer sur le bouton Properties... avant de faire Ok pour choisir les paramètres du port (numéro COM,...) au cas où ils auraient changé. L'appui sur Properties ou sur Ok produit l'exécution de code au niveau du driver ASCOM choisi.
Le clic sur ce bouton Properties produit l'affichage de la boite de dialogue programmée dans le fichier Driver.cs du driver. Cette boite de dialogue est la méthode SetupDialog de la public class FilterWheel qui code le driver.
Le code, que l'on doit compléter, fournit par défaut la popup ci-dessus. Il est situé dans le fichier SetupDialogForm.cs modifiable en [Design] et/ou en code.
Le code par défaut remplit la liste des ports COM disponibles sur le P dans la méthode InitUI() de la classe SetupDialogForm. Si l'affichage convient, il n'y a rien à modifier dans cette classe.
Lorsque l'utilisateur à choisi le port COM et éventuellement coché la case qui permet d'enregistrer un log dans C:\Users\nom_userr\Documents\ASCOM, puis cliqué sur Ok, il y a retour à l'affichage de la popup ASCOM FilterWheel Chooser, où l'utilisateur va cliquer sur Ok, s'il est satisfait de son choix.
A ce stade le périphérique n'est pas connecté à l'application cliente via le driver, il est simplement choisi. Pour se connecter l'application cliente doit :
•.créer un nouvel objet ASCOM.DriverAccess.FilterWheel par exemple comme ceci :
driver = new ASCOM.DriverAccess.FilterWheel(Properties.Settings.Default.DriverId);
•.puis demander la connexion au driver en appelant la méthode Connected, par ex. comme ceci :
driver.Connected = true;
Dans le driver, pour réaliser cette connexion le programmeur doit compléter la méthode set de public bool Connected et la méthode private bool IsConnected qui y est utilisée (pas que là).
Mais en premier lieu, il doit ajouter en global la définition de l'objet de la classe ASCOM.Utilities.Serial qui servira à la connexion :
private ASCOM.Utilities.Serial objSerial = new ASCOM.Utilities.Serial();
La méthode IsConnected peut être la suivante :
private bool IsConnected
{
get
{
connectedState = objSerial != null && objSerial.Connected; // Ajout MLL
return connectedState;
}
}
et set et get de Connected peuvent être programmés comme suit :
public bool Connected
{
get {return IsConnected;}
set
{
if (value == IsConnected) return; // déjà comme désiré
if (value) // demande de connexion
{
objSerial = new ASCOM.Utilities.Serial();
objSerial.PortName = comPort;
objSerial.Speed = SerialSpeed.ps9600;
objSerial.Connected = connectedState = true;
}
else // demande de déconnexion
{ objSerial.Connected = connectedState = false; }
}
}
Pour la communication entre le driver et le périphérique, la fonction la plus importante est la méthode public string CommandString(string command, bool raw).
De prime abord, elle n'est pas instanciée et son appel produit une exception.
public string CommandString(string command, bool raw)
{
CheckConnected("CommandString");
// TODO The optional CommandString method should either be implemented OR throw a MethodNotImplementedException
// If implemented, CommandString must send the supplied command to the mount and wait for a response before returning this to the client
throw new ASCOM.MethodNotImplementedException("CommandString");
}
Voici une modification possible :
public string CommandString(string command, bool raw)
{
CheckConnected("CommandString");
objSerial.Transmit(command);
string ret = objSerial.ReceiveTerminated("#");
return ret.Remove(ret.Length - 1, 1);
}
qui envoie le string command sur la liaison et attend la réponse qui doit se terminer par le caractère #. Cette réponse est retournée à l'appelant, mais sans ce caractère de terminaison.
A priori la grande majorité des commandes et communications avec le périphérique peuvent se faire par l'intermédiaire de cette méthode.
Dans le cas de la roue à filtre les commandes principales sont les set et get de la méthode Position. Elles sont réalisées en modifiant le code original comme suit :
public short Position
{
get {
string ret = CommandString("GETFILTER#", true);
fwPosition = short.Parse(ret);
return fwPosition;
}
set {
if ((value < 0) | (value > fwNames.Length - 1))
throw new InvalidValueException("Numéro filtre invalide");
fwPosition = value;
CommandString(string.Format("FILTER{0}#",value), true);
}
}
Pour que le driver soit simultanément accessible par plusieurs applications, ou pour coupler plusieurs drivers sur une même liaison série on doit créer un ASCOM Local Server Driver. La méthode à suivre est décrite dans le menu général ASCOM rubrique "How to create a local server based driver", ainsi que dans https://td0g.ca/2019/01/10/writing-an-ascom-local-server-driver/ que j'ai recopié dans ma doc "Serveur driver ASCOM.odt".
Le driver généré par VS+ASCOM en mode Debug est automatiquement enregistré dans ASCOM et dans la base des registres et est de ce fait accessible en interne par toutes les applis clientes. Si on fait un Clean de la solution cela efface ces accès (je n'ai pas vérifié). Si on compile le driver en mode Release, il n'est plus accessible ! Il faut l'installer de manière classique comme décrit ci-après.
Allez dans le menu Windows et dans Ascom cliquer sur le wizard Driver Install Script Generator qui affiche la fenêtre suivante (à droite : après mes entrées) :
Cliquer sur Save génère le script mllFW Setup.iss (iss pour Inno Setup Script) dans le répertoire source indiqué et l'affiche dans la fenêtre d'InnoSetup :
;
; Script generated by the ASCOM Driver Installer Script Generator 6.6.0.0
; Generated by mll on 2023-10-30 (UTC)
;
[Setup]
AppID={{679ffda8-feb1-43e1-92ae-6820f71a1b79}
AppName=ASCOM mllFW FilterWheel Driver
AppVerName=ASCOM mllFW FilterWheel Driver 1.0
AppVersion=1.0
AppPublisher=mll <michel@llibre.fr>
AppPublisherURL=mailto:michel@llibre.fr
AppSupportURL=https://ascomtalk.groups.io/g/Help
AppUpdatesURL=https://ascom-standards.org/
VersionInfoVersion=1.0.0
MinVersion=6.1.7601
DefaultDirName="{cf}\ASCOM\FilterWheel"
DisableDirPage=yes
DisableProgramGroupPage=yes
OutputDir="."
OutputBaseFilename="mllFW Setup"
Compression=lzma
SolidCompression=yes
; Put there by Platform if Driver Installer Support selected
WizardImageFile="C:\Program Files (x86)\ASCOM\Platform 6 Developer Components\Installer Generator\Resources\WizardImage.bmp"
LicenseFile="C:\Program Files (x86)\ASCOM\Platform 6 Developer Components\Installer Generator\Resources\CreativeCommons.txt"
; {cf}\ASCOM\Uninstall\FilterWheel folder created by Platform, always
UninstallFilesDir="{cf}\ASCOM\Uninstall\FilterWheel\mllFW"
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Dirs]
Name: "{cf}\ASCOM\Uninstall\FilterWheel\mllFW"
; TODO: Add subfolders below {app} as needed (e.g. Name: "{app}\MyFolder")
[Files]
Source: "C:\Users\Utilisateur\source\repos\fwDriver\bin\Release\ASCOM.mllFW.FilterWheel.dll"; DestDir: "{app}"
; Require a read-me HTML to appear after installation, maybe driver's Help doc
Source: "C:\Users\Utilisateur\source\repos\fwDriver\ReadMe.htm"; DestDir: "{app}"; Flags: isreadme
; TODO: Add other files needed by your driver here (add subfolders above)
; Only if driver is .NET
[Run]
; Only for .NET assembly/in-proc drivers
Filename: "{dotnet4032}\regasm.exe"; Parameters: "/codebase ""{app}\ASCOM.mllFW.FilterWheel.dll"""; Flags: runhidden 32bit
Filename: "{dotnet4064}\regasm.exe"; Parameters: "/codebase ""{app}\ASCOM.mllFW.FilterWheel.dll"""; Flags: runhidden 64bit; Check: IsWin64
; Only if driver is .NET
[UninstallRun]
; Only for .NET assembly/in-proc drivers
Filename: "{dotnet4032}\regasm.exe"; Parameters: "-u ""{app}\ASCOM.mllFW.FilterWheel.dll"""; Flags: runhidden 32bit
; This helps to give a clean uninstall
Filename: "{dotnet4064}\regasm.exe"; Parameters: "/codebase ""{app}\ASCOM.mllFW.FilterWheel.dll"""; Flags: runhidden 64bit; Check: IsWin64
Filename: "{dotnet4064}\regasm.exe"; Parameters: "-u ""{app}\ASCOM.mllFW.FilterWheel.dll"""; Flags: runhidden 64bit; Check: IsWin64
[Code]
const
REQUIRED_PLATFORM_VERSION = 6.2; // Set this to the minimum required ASCOM Platform version for this application
//
// Function to return the ASCOM Platform's version number as a double.
//
function PlatformVersion(): Double;
var
PlatVerString : String;
begin
Result := 0.0; // Initialise the return value in case we can't read the registry
try
if RegQueryStringValue(HKEY_LOCAL_MACHINE_32, 'Software\ASCOM','PlatformVersion', PlatVerString) then
begin // Successfully read the value from the registry
Result := StrToFloat(PlatVerString); // Create a double from the X.Y Platform version string
end;
except
ShowExceptionMessage;
Result:= -1.0; // Indicate in the return value that an exception was generated
end;
end;
//
// Before the installer UI appears, verify that the required ASCOM Platform version is installed.
//
function InitializeSetup(): Boolean;
var
PlatformVersionNumber : double;
begin
Result := FALSE; // Assume failure
PlatformVersionNumber := PlatformVersion(); // Get the installed Platform version as a double
If PlatformVersionNumber >= REQUIRED_PLATFORM_VERSION then // Check whether we have the minimum required Platform or newer
Result := TRUE
else
if PlatformVersionNumber = 0.0 then
MsgBox('No ASCOM Platform is installed. Please install Platform ' + Format('%3.1f', [REQUIRED_PLATFORM_VERSION]) + ' or later from https://www.ascom-standards.org', mbCriticalError, MB_OK)
else
MsgBox('ASCOM Platform ' + Format('%3.1f', [REQUIRED_PLATFORM_VERSION]) + ' or later is required, but Platform '+ Format('%3.1f', [PlatformVersionNumber]) + ' is installed. Please install the latest Platform before continuing; you will find it at https://www.ascom-standards.org', mbCriticalError, MB_OK);
end;
// Code to enable the installer to uninstall previous versions of itself when a new version is installed
procedure CurStepChanged(CurStep: TSetupStep);
var
ResultCode: Integer;
UninstallExe: String;
UninstallRegistry: String;
begin
if (CurStep = ssInstall) then // Install step has started
begin
// Create the correct registry location name, which is based on the AppId
UninstallRegistry := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#SetupSetting("AppId")}' + '_is1');
// Check whether an extry exists
if RegQueryStringValue(HKLM, UninstallRegistry, 'UninstallString', UninstallExe) then
begin // Entry exists and previous version is installed so run its uninstaller quietly after informing the user
MsgBox('Setup will now remove the previous version.', mbInformation, MB_OK);
Exec(RemoveQuotes(UninstallExe), ' /SILENT', '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode);
sleep(1000); //Give enough time for the install screen to be repainted before continuing
end
end;
end;
Compilation :
La compilation de ce script génère l'exécutable de déploiement mllFW Setup.exe.
Compilation et Exécution : sur la machine locale.
Voici les principales étapes de cette exécution :
Creating directory: C:\Program Files (x86)\Common Files\ASCOM\Uninstall\FilterWheel
Creating directory: C:\Program Files (x86)\Common Files\ASCOM\Uninstall\FilterWheel\mllFW
Directory for uninstall files: C:\Program Files (x86)\Common Files\ASCOM\Uninstall\FilterWheel\mllFW
Creating new uninstall log: C:\Program Files (x86)\Common Files\ASCOM\Uninstall\FilterWheel\mllFW\unins000.dat
-- File entry --
Dest filename: C:\Program Files (x86)\Common Files\ASCOM\Uninstall\FilterWheel\mllFW\unins000.exe
Time stamp of our file: 2023-10-30 11:07:02.761
Installing the file.
Successfully installed the file.
-- File entry --
Dest filename: C:\Program Files (x86)\Common Files\ASCOM\FilterWheel\ASCOM.mllFW.FilterWheel.dll
Time stamp of our file: 2023-10-30 10:56:12.000
Installing the file.
Successfully installed the file.
-- File entry --
Dest filename: C:\Program Files (x86)\Common Files\ASCOM\FilterWheel\ReadMe.htm
Time stamp of our file: 2023-10-15 22:26:50.000
Installing the file.
Successfully installed the file.
Saving uninstall information.
Creating new uninstall key: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\{679ffda8-feb1-43e1-92ae-6820f71a1b79}_is1
Writing uninstall key values.
Detected previous non administrative install? No
Detected previous administrative 64-bit install? No
Installation process succeeded.
-- Run entry --
Run as: Current user
Type: Exec
Filename: C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe
Parameters: /codebase "C:\Program Files (x86)\Common Files\ASCOM\FilterWheel\ASCOM.mllFW.FilterWheel.dll"
Process exit code: 0
-- Run entry --
Run as: Current user
Type: Exec
Filename: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\regasm.exe
Parameters: /codebase "C:\Program Files (x86)\Common Files\ASCOM\FilterWheel\ASCOM.mllFW.FilterWheel.dll"
Process exit code: 100
Need to restart Windows? No
-- Run entry --
Run as: Original user
Type: ShellExec
Filename: C:\Program Files (x86)\Common Files\ASCOM\FilterWheel\ReadMe.htm
Deinitializing Setup.
*** Setup exit code: 0
En résumé :
•.Création des fichiers de désintallation dans la zone ASCOM :
C:\Program Files (x86)\Common Files\ASCOM\Uninstall\FilterWheel\mllFW\unins000.exe et unins000.dat
•.Mise en place des fichiers du driver dans la zone ASCOM :
C:\Program Files (x86)\Common Files\ASCOM\FilterWheel\ASCOM.mllFW.FilterWheel.dll
C:\Program Files (x86)\Common Files\ASCOM\FilterWheel\ReadMe.htm
•.Insciption information de désinstallation dans le registre :
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\{679ffda8-feb1-43e1-92ae-6820f71a1b79}_is1
•.Enregistrement de l'assembly dans le registre :
C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe /codebase "C:\Program Files (x86)\Common Files\ASCOM\FilterWheel\ASCOM.mllFW.FilterWheel.dll
deux fois de suite. La première fois retourne 0 (ok) et la deuxième 100 (peut-être déjà enregistré ?).
•.Affichage du readm.htm.
Transfert et Exécution :
Si on copie le fichier d'installation sur une autre PC Windows, il y a de fortes chances que l'antivirus le refuse et le détruise. J'ai du désactiver Windows Defender pour pouvoir l'exécuter. L'exécution réalise sur le nouveau PC les étapes décrites ci-avant. Le driver est disponible sur le nouveau PC !
Pour faire un exemple complet, le périphérique choisi est une roue à filtré simulée sur une carte Arduino.
Ce simulateur ne reconnaît que 2 commandes reçues via la liaison USB :
•.GETFILTER# à laquelle il répond par n# où n est le numéro du filtre actuellement en place, (0 1,..,4) ou bien -1# s'il est en train de changer de filtre.
•.FILTERn# qui est l'ordre de mettre en place le filtre numéro n où n doit valoir 0, 1,...4. L'Arduino répond par le caractère # à cette commande.
Le changement de filtre ne se fait qu'au bout de 2 secondes. L'application doit envoyer des GETFILTER# pour s’assurer de la réalisation d'une commande de changement de filtre. Tant qu'elle reçoit -1# en réponse c'est que le changement est en cours.
Voici le programme testé :
int currentFilter = 0;
unsigned long tnew;
void setup()
{
Serial.begin(9600);
Serial.flush();
tnew = millis();
}
void loop()
{
String cmd;
if (Serial.available() > 0)
{
cmd = Serial.readStringUntil('#');
if (cmd == "GETFILTER") {
if (millis()>= tnew) Serial.print(currentFilter);
else Serial.print(-1);
}
else if (cmd == "FILTER0") MoveFilter(0);
else if (cmd == "FILTER1") MoveFilter(1);
else if (cmd == "FILTER2") MoveFilter(2);
else if (cmd == "FILTER3") MoveFilter(3);
Serial.println("#");
}
}
void MoveFilter(int pos) {
if (pos == currentFilter) Serial.print(currentFilter);
else { tnew = millis() + 2000; Serial.print(-1);}
currentFilter = pos;
}
Après avoir compilé et chargé ce programme dans l'Arduino, ouvrir une console liaison série genre Termite sur le bon port Série de l'Arduino et tester les commandes suivantes :
GETFILTER#
FILTER2#
GETFILTER#
etc...
Le Driver a 2 parties :
•.Le driver proprement dit qui génère une dll
•.Une application de test qui est un exe qui permet de mettre au point la dll.
On peut rencontrer divers problèmes de configurations. Pour les éviter :
•.Démarrer VS en mode administrateur chaque fois que le driver doit être compilé, sinon il y a un problème d'enregistrement de la dll.
•.Vérifier que la dll et l'application utilisent le même .NET Framework 4.7.2 par ex (onglet Application des Propriétés)
•.Vérifier que la dll et l'application sont sur la même plateforme cible x86 (onglet Build des Propriétés)
Avec VS sélectionner "Créer un projet", puis dans les boutons/listes de filtre en haut à droite :
•.langage : c#
•.plateformes : Windows
•.type de projet : ASCOM
et dans les items proposés prendre : ASCOM device driver (c#)
Choisir un nom pour le projet (fwDriver) et dans la liste des classes périphériques proposées choisir : FilterWheel et donner un nom au driver : mllFW. Retenir ce nom qui utilisé pour générer le nom du driver, à savoir ASCOM.mllFW.FilterWheel
Suivre les indications du fichier Readme.htm.
Choisir Debug puis Générer FwDriver ce qui doit se faire sans erreur. Ensuite avant de personnaliser le driver, le fichier Readme.htm préconise de créer une application client qui servira de projet de démarrage pour tester le driver (voir section suivante).
Voici les principales modifications à apporter au code Driver.cs du driver générique. Il s'agit essentiellement de la communication :
•.Déclarer l'objet qui servira à gérer la liaison série :
private ASCOM.Utilities.Serial objSerial = new ASCOM.Utilities.Serial();
•.Mettre le bon code dans la méthode Isconnected, en utilisant les méthodes de notre objet : :
•.Mettre le bon code dans les accès get et set de la méthode Connected.
•.Mettre le bon code dans la fonction CommandString qui est la seule que nous utilisons pour communiquer avec le périphérique
•.Mettre le bon code dans les accès get et set de la méthode Position (on utilise CommandString pour cela).
Et voici le listing complet de Driver.cs dont j'ai ôté les commentaires et directives d'affichage :
#define FilterWheel
using ASCOM;
using ASCOM.Astrometry;
using ASCOM.Astrometry.AstroUtils;
using ASCOM.DeviceInterface;
using ASCOM.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
namespace ASCOM.mllFW
{
[Guid("c924e3d5-ac14-46b6-878e-a6cbf1842925")]
[ClassInterface(ClassInterfaceType.None)]
public class FilterWheel : IFilterWheelV2
{
internal static string driverID = "ASCOM.mllFW.FilterWheel";
private static string driverDescription = "ASCOM FilterWheel Driver for mllFW.";
private ASCOM.Utilities.Serial objSerial = new ASCOM.Utilities.Serial();
internal static string comPortProfileName = "COM Port";
internal static string comPortDefault = "COM1";
internal static string traceStateProfileName = "Trace Level";
internal static string traceStateDefault = "false";
internal static string comPort;
private bool connectedState;
private Util utilities;
private AstroUtils astroUtilities;
internal TraceLogger tl;
public FilterWheel()
{
tl = new TraceLogger("", "mllFW");
ReadProfile();
tl.LogMessage("FilterWheel", "Starting initialisation");
connectedState = false;
utilities = new Util();
astroUtilities = new AstroUtils();
tl.LogMessage("FilterWheel", "Completed initialisation");
}
public void SetupDialog()
{
if (IsConnected)
System.Windows.Forms.MessageBox.Show("Already connected, just press OK");
using (SetupDialogForm F = new SetupDialogForm(tl))
{
var result = F.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK) WriteProfile();
}
}
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");
throw new ASCOM.MethodNotImplementedException("CommandBlind");
}
public bool CommandBool(string command, bool raw)
{
CheckConnected("CommandBool");
throw new ASCOM.MethodNotImplementedException("CommandBool");
}
public string CommandString(string command, bool raw)
{
CheckConnected("CommandString");
objSerial.Transmit(command);
string ret = objSerial.ReceiveTerminated("#");
return ret.Remove(ret.Length - 1, 1);
}
public void Dispose()
{
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)
{
connectedState = true;
LogMessage("Connected Set", "Connecting to port {0}", comPort);
objSerial = new ASCOM.Utilities.Serial();
objSerial.PortName = comPort;
objSerial.Speed = SerialSpeed.ps9600;
objSerial.Connected = true;
}
else
{
connectedState = false;
LogMessage("Connected Set", "Disconnecting from port {0}", comPort);
objSerial.Connected = false;
}
}
}
public string Description
{
get
{
tl.LogMessage("Description Get", driverDescription);
return driverDescription;
}
}
public string DriverInfo
{
get
{
Version version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
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
{
get
{
LogMessage("InterfaceVersion Get", "2");
return Convert.ToInt16("2");
}
}
public string Name
{
get
{
string name = driverDescription; // Modif MLL
tl.LogMessage("Name Get", name);
return name;
}
}
private int[] fwOffsets = new int[4] { 0, 0, 0, 0 };
private string[] fwNames = new string[4] { "Red", "Green", "Blue", "Clear" };
private short fwPosition = 0;
public int[] FocusOffsets
{
get
{
foreach (int fwOffset in fwOffsets)
tl.LogMessage("FocusOffsets Get", fwOffset.ToString());
return fwOffsets;
}
}
public string[] Names
{
get
{
foreach (string fwName in fwNames)
tl.LogMessage("Names Get", fwName);
return fwNames;
}
}
public short Position
{
get
{
string ret = CommandString("GETFILTER#", true);
fwPosition = short.Parse(ret);
tl.LogMessage("Position Get", fwPosition.ToString());
return fwPosition;
}
set
{
tl.LogMessage("Position Set", value.ToString());
if ((value < 0) | (value > fwNames.Length - 1))
{
tl.LogMessage("", "Throwing InvalidValueException - Position: " + value.ToString() + ", Range: 0 to " + (fwNames.Length - 1).ToString());
throw new InvalidValueException("Position", value.ToString(), "0 to " + (fwNames.Length - 1).ToString());
}
fwPosition = value;
CommandString(string.Format("FILTER{0}#",value), true);
}
}
private static void RegUnregASCOM(bool bRegister)
{
using (var P = new ASCOM.Utilities.Profile())
{
P.DeviceType = "FilterWheel";
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);
}
private bool IsConnected
{
get
{
connectedState = objSerial != null && objSerial.Connected;
return connectedState;
}
}
private void CheckConnected(string message)
{
if (!IsConnected)
{
throw new ASCOM.NotConnectedException(message);
}
}
internal void ReadProfile()
{
using (Profile driverProfile = new Profile())
{
driverProfile.DeviceType = "FilterWheel";
tl.Enabled = Convert.ToBoolean(driverProfile.GetValue(driverID, traceStateProfileName, string.Empty, traceStateDefault));
comPort = driverProfile.GetValue(driverID, comPortProfileName, string.Empty, comPortDefault);
}
}
internal void WriteProfile()
{
using (Profile driverProfile = new Profile())
{
driverProfile.DeviceType = "FilterWheel";
driverProfile.WriteValue(driverID, traceStateProfileName, tl.Enabled.ToString());
driverProfile.WriteValue(driverID, comPortProfileName, comPort.ToString());
}
}
internal void LogMessage(string identifier, string message, params object[] args)
{
var msg = string.Format(message, args);
tl.LogMessage(identifier, msg);
}
#endregion
}
}
Dans VS faire Menu Fichier/Ajouter/Nouveau projet ou clic droit sur la solution fwDriver dans l'explorateur de solutions et chois "Ajouter/Nouveau projet : j'ai choisi une ASCOM Driver Test Forms App nommée fwAPP, et j'ai sélectionné FilterWheel, puis mllFW.
Dans l'Exploration de solution, Clic droit sur fwAPP et sélectionner "Définir en tant que projet de démarrage". Dans les propriétés modifier le type de Sortie en Application Windows, puis tester la Générer la solution.
Pour tester la roue à filtre simulée, j'ai personnalisé la fenêtre affichée par l'application (fichiers Form1.cs) en y ajoutant des boutons "Previous" et "Next" pour changer de filtre et deux Textbox affichant le numéro du filtre et le nombre de filtres.
Voici la fenêtre modifiée :
et voici son code :
using System;
using System.Windows.Forms;
using System.Threading;
namespace ASCOM.mllFW
{
public partial class Form1 : Form
{
private ASCOM.DriverAccess.FilterWheel driver;
private int nbFiltres;
public Form1()
{
InitializeComponent();
SetUIState();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (IsConnected)
driver.Connected = false;
Properties.Settings.Default.Save();
}
private void buttonChoose_Click(object sender, EventArgs e)
{
Properties.Settings.Default.DriverId = ASCOM.DriverAccess.FilterWheel.Choose(Properties.Settings.Default.DriverId);
SetUIState();
}
private void buttonConnect_Click(object sender, EventArgs e)
{
if (IsConnected)
{
driver.Connected = false;
}
else
{
driver = new ASCOM.DriverAccess.FilterWheel(Properties.Settings.Default.DriverId);
driver.Connected = true;
nbFiltres = driver.Names.Length;
labelNbFiltres.Text = nbFiltres.ToString();
Thread.Sleep(1000); // Pour éviter un timeout !!
labelNumero.Text = driver.Position.ToString();
}
SetUIState();
}
private void SetUIState()
{
buttonConnect.Enabled = !string.IsNullOrEmpty(Properties.Settings.Default.DriverId);
bool c = IsConnected;
buttonChoose.Enabled = !c;
buttonConnect.Text = c ? "Disconnect" : "Connect";
}
private bool IsConnected
{
get
{
return ((this.driver != null) && (driver.Connected == true));
}
}
private void buttonNext_Click(object sender, EventArgs e)
{
movePosition(1);
}
private void buttonPreviuos_Click(object sender, EventArgs e)
{
movePosition(-1);
}
private void movePosition(int n)
{
labelNumero.Text = "moving";
labelNumero.Refresh(); // Sinon, pas affiché : effacé par l'affichage suivant.
int newpos = driver.Position + n;
if (newpos < 0) newpos = nbFiltres - 1;
if (newpos >= nbFiltres) newpos = 0;
driver.Position = (short)newpos;
while (true)
{
newpos = driver.Position;
if (newpos != -1) break;
Thread.Sleep(200);
}
labelNumero.Text = newpos.ToString();
}
}
}