An dieser Stelle will ich nun das Konzept ausführlich vorstellen und zur Diskussion stellen. Alle sind herzlich eingeladen Vorschläge und Kritik fast jeder Art hier abzugeben. Ich bitte allerdings um zielgerichtete Kritik.
Das 1.x API hat mehrere Einschränkungen und Schwächen. Das 2.0 API soll diese Probleme lösen, ohne dabei komplizierter zu werden. Als Zugabe bleibt das 1.x API erhalten.
Das ist natürlich ein hehres Ziel das man nur verfehlen kann, denn gerade die Handhabung des Ein- und Aussteckens von IO-Warriors wurde im 1.x API als zu kompliziert für den Gelegenheitsprogrammierer weggelassen.
Die Neuerungen:
1. dynamische Geräteverwaltung
Das Ein- und Ausstecken von IO-Warriors wird erkannt und gemeldet.
2. virtuelle Teilgeräte
Die IO-Warrior werden als Teilgeräte verwaltet. Eine Special Mode Function stellt sich als eigenes Gerät dar.
3. korrekte Ressource-Verwaltung
Ein Teilgerät wird von einem Programm belegt und ist dann für andere Programme gesperrt.
4. asynchrone Daten-Ereignisse
Die Key Matrix lässt sich mit dem 1.x API nicht richtig handhaben, da die Reports asynchrone Ereignisse darstellen, die zu beliebigem Zeitpunkt spontan eintreffen können.
5. einfachere Datenübergabe
Der reale Datenaustausch erfolgt über Reports. Das bisherige Konzept mit Puffern und Längen ist nicht einfach genug.
6. Erweiterbarkeit
Unterstützung zukünftiger neuer Mitglieder der IO-Warrior-Familie, die mehr und neue Funktionalität bereitstellen.
Die Einzelheiten dazu:
1.
Um das Ein- und Ausstecken der Geräte zu erkennen muss das API mit einer Callback-Funktion ausgerüstet werden. Es wird also bei der Initialisierungsfunktion des APIs eine Funktion übergeben, die jedesmal aufgerufen wird wenn ein IO-Warrior ein- oder ausgesteckt wird. Für bereits beim Aufruf der Initialisierungsfunktion eingesteckte IO-Warriors wird das Einstecken einfach simuliert.
2.
Ein IO-Warrior wird als mehrere virtuelle Teilgeräte verwaltet. Die IO-Pins und jede Special Mode Function wird als eigenes Gerät verwaltet. Dies erlaubt es einem Programm nur einen Teil des IO-Warriors zu benutzen und die anderen Teile bleiben für andere Programme verfügbar.
Das Einstecken eines IO-Warriors bewirkt nun das Eintreffen mehrerer Teilgeräte. Der Callback wird also mehrmals aufgerufen.
3.
Bisher konnte jedes Programm auf alle vorhandenen IO-Warrior zugreifen, ohne von den anderen Programmen zu wissen. Dabei konnte es natürlich zu Konflikten kommen.
Im 2.0 API muss man das Teilgerät zuerst belegen bevor es benutzt werden kann. Damit ist das Teilgerät den anderen Programmen entzogen. Die Belegung erfolgt im Callback. Dabei tritt eine gewisse Ungerechtigkeit auf. Ein freies Teilgerät wird den Programmen nacheinander vorgelegt. Wer also in dieser Daisy-Chain hinten steht dem kann das Teilgerät vor der Nase weggeschnappt werden. Dies ist allerdings nur mässig problematisch, da keine Strategie gerecht sein kann.
Zukünftige Programme sollten sich merken welchen IO-Warrior sie benutzen wollen. VID, PID und Seriennummer identifizieren einen IO-Warrior weltweit eindeutig. Da es nun bereits einige Produkte auf Basis von IO-Warriors gibt, wird es zunehmend wichtig das man nicht den ersten vorhandenen greift. Es ist sonst zu leicht möglich das man mit dem falschen IO-Warrior reden will.
4.
Die Special Mode Functions waren ursprünglich als Kommandointerface konzipiert, d. h. man schreibt ein Kommando an den IO-Warrior und erhält eine Antwort. Die Key-Matrix-Funktion hat dieses Konzept allerdings durchbrochen. Es kann jederzeit ein Report durch ein externes Ereignis (Tastendruck) ausgelöst werden.
Das 1.x API ist nicht für dieses Konzept geeignet. Das 2.0 API löst das Problem wieder durch einen Callback. Es gibt nun einen Daten-Callback, der für das virtuelle Teilgerät ausgelöst wird sobald ein Report eintrifft. Intern läuft dazu ein Lesethread. Im Daten-Callback kann man nun den Report direkt annehmen oder ihn zurücklegen lassen. Legt man den Report zurück, so wird er in eine Lesewarteschlange eingestellt und kann dann mit der normalen Read-Funktion des APIs abgeholt werden. Auf diese Weise kann man daher wahlweise weiterhin mit einfachen Write/Read-Paaren operieren oder die Reports per Callback verarbeiten.
5.
Die Daten werden im 2.0 API in einzelnen Reports gelesen und geschrieben. Es wird dabei ein Maximal-Report benutzt, d. h. der Report ist gross genug für jeden IO-Warrior. Das API kann selbst entscheiden wieviele Bytes des Reports gültig sind, da immer klar ist woher bzw wohin die Daten kommen und gehen.
6.
Ein Nebeneffekt der virtuellen Teilgeräte ist die elegante Handhabung älterer und neuerer Chiprevisionen. Nicht vorhandene Special Mode Functions werden einfach durch das Fehlen des entsprechenden Teilgerätes dargestellt. Neue Special Mode Functions ergeben ein weiteres Teilgerät.
Hier erst mal der Entwurf des 2.0 APIs als ganzes. Im folgenden werde ich dann noch die Einzelheiten durchsprechen. Ein paar Zeilen habe ich herausgekürzt, da sie nicht wichtig sind (oder zuviel verraten :).
Code: Select all
// values for action parameter of IOWKIT2_DEVICECHANGE_EVENT
#define IOWKIT2_ACTION_ARRIVAL 0
#define IOWKIT2_ACTION_REMOVAL 1
// max number of bytes in a report
#define IOWKIT2_REPORT_PAYLOAD_SIZE 63
// virtual IO-Warrior device kinds
#define IOWKIT2_KIND_HARDWARE 0
#define IOWKIT2_KIND_IO_PINS 1
#define IOWKIT2_KIND_IIC 2
#define IOWKIT2_KIND_LCD 3
#define IOWKIT2_KIND_SPI 4
#define IOWKIT2_KIND_INFRARED 5
#define IOWKIT2_KIND_LED_MATRIX 6
#define IOWKIT2_KIND_SWITCH_MATRIX 7
// Opaque IO-Warrior handle
typedef PVOID IOWKIT2_HANDLE;
// This is a max report which can hold any IO-Warrior report
typedef struct _IOWKIT2_REPORT
{
UCHAR ReportID;
UCHAR Bytes[IOWKIT2_REPORT_PAYLOAD_SIZE];
}
IOWKIT2_REPORT, *PIOWKIT2_REPORT;
#define IOWKIT2_REPORT_SIZE sizeof(IOWKIT2_REPORT)
typedef struct _IOWKIT2_CONFIGINFO
{
ULONG Kind;
ULONG Reason;
IOWKIT2_REPORT Mask;
}
IOWKIT2_CONFIGINFO, *PIOWKIT2_CONFIGINFO;
typedef struct _IOWKIT2_HARDWAREINFO
{
// hardware info
ULONG VendorID;
ULONG ProductID;
ULONG SerialNumber;
ULONG Revision;
ULONG InputReportByteLength;
ULONG OutputReportByteLength;
BOOL IsConnected; // the hardware is still connected
}
IOWKIT2_HARDWAREINFO, *PIOWKIT2_HARDWAREINFO;
typedef struct _IOWKIT2_DEVICESTATE
{
// virtual device info
ULONG Kind; // one of the IOWKIT2_KIND_* values
BOOL IsActive; // the special mode is switched on
}
IOWKIT2_DEVICESTATE, *PIOWKIT2_DEVICESTATE;
typedef struct _IOWKIT2_DEVICEPARAMETERS
{
// device parameters changeable through IowKit2SetDeviceParameters
BOOL IsUsed; // the device is in use by the calling application
IOWKIT2_REPORT Mask; // used to reserve specific IO-Pins
ULONG PendingReports; // number of reports waiting in the input queue
ULONG ReadTimeout; // timeout for IowKit2Read
ULONG WriteTimeout; // timeout for IowKit2Write
IOWKIT2_DATA_EVENT DataEvent; // callback for incoming reports
PVOID Context; // free for any use
}
IOWKIT2_DEVICEPARAMETERS, *PIOWKIT2_DEVICEPARAMETERS;
typedef struct _IOWKIT2_DEVICEINFO
{
IOWKIT2_HARDWAREINFO Hardware;
IOWKIT2_DEVICESTATE State;
IOWKIT2_DEVICEPARAMETERS Parameters;
}
IOWKIT2_DEVICEINFO, *PIOWKIT2_DEVICEINFO;
typedef BOOL (IOWKIT2_API *IOWKIT2_DATA_EVENT)(IOWKIT2_HANDLE devHandle, PIOWKIT2_REPORT report, ULONG size);
typedef void (IOWKIT2_API *IOWKIT2_DEVICECHANGE_EVENT)(ULONG action, IOWKIT2_HANDLE devHandle, PVOID Context);
typedef void (IOWKIT2_API *IOWKIT2_CONFIGCHANGE_EVENT)(IOWKIT2_HANDLE devHandle, PIOWKIT2_CONFIGINFO oldConfig, PIOWKIT2_CONFIGINFO newConfig);
DWORD IOWKIT2_API IowKit2Initialize(IOWKIT2_DEVICECHANGE_EVENT DevChangeEvent, IOWKIT2_CONFIGCHANGE_EVENT ConfigChangeEvent, PVOID Context);
void IOWKIT2_API IowKit2Finalize(void);
void IOWKIT2_API IowKit2Enumerate(void);
ULONG IOWKIT2_API IowKit2GetNumDevs(ULONG Kind, BOOL Logical);
int IOWKIT2_API IowKit2Write(IOWKIT2_HANDLE devHandle, PIOWKIT2_REPORT report);
int IOWKIT2_API IowKit2Read(IOWKIT2_HANDLE devHandle, PIOWKIT2_REPORT report);
int IOWKIT2_API IowKit2ReadNonBlocking(IOWKIT2_HANDLE devHandle, PIOWKIT2_REPORT report);
int IOWKIT2_API IowKit2ReadImmediate(IOWKIT2_HANDLE devHandle, PIOWKIT2_REPORT report);
BOOL IOWKIT2_API IowKit2GetDeviceInfo(IOWKIT2_HANDLE devHandle, PIOWKIT2_DEVICE devInfo);
BOOL IOWKIT2_API IowKit2SetDeviceParameters(IOWKIT2_HANDLE devHandle, PIOWKIT2_DEVICEINFO devInfo);
PCSTR IOWKIT2_API IowKit2Version(void);
Mit IowKit2Initialize() meldet man sich zur Benutzung von IO-Warriors an. Es wird ein IOWKIT2_DEVICECHANGE_EVENT übergeben, der dann prompt aufgerufen wird sofern freie IO-Warriors vorhanden sind. Das bezieht sich natürlich auf virtuelle Teilgeräte. Mit IowKit2Finalize() meldet man sich wieder ab. Alle benutzten Teilgeräte werden wieder freigegeben und entsprechend bekommen andere Programme per Callback die nun wieder vorhandenen Teilgeräte gemeldet.
Mit IowKit2Enumerate() kann man jederzeit sich alle freien Geräte per Callback melden lassen. Es ist nämlich nicht möglich ausserhalb des Callbacks ein Teilgerät zu belegen.
IowKit2GetNumDevs() meldet die Anzahl vorhandener Geräte oder Teilgeräte. Mit dem Parameter Kind kann man angeben welche Teilgeräte bzw Geräte gezählt werden sollen. IOWKIT2_KIND_HARDWARE zählt alle echten IO-Warriors egal von welcher Art. Mit der ProductID als Parameterwert kan man die echten IO-Warrior nach Art zählen, also beispielsweise nur alle vorhandenen IO-Warrior 40. Die anderen IOWKIT2_KIND_ Konstanten zählen virtuelle Teilgeräte. Für sie gibt der Parameter Logical an ob alle vorhandenen oder nur die vom eigenen Programm belegten plus die noch freien Teilgeräte gezählt werden.
Die nächste Gruppe von Funktionen betrifft das Lesen und Schreiben. Dazu ist zu sagen das beim Schreiben abgesichert wird das nur für das virtuelle Teilgerät erlaubte ReportIDs geschrieben werden. Beim Lesen werden entsprechend nur die für das Teilgerät gedachten Reports geliefert. IowKit2ReadNonBlocking() liest nicht-blockierend, d. h. wenn kein Report wartet, dann kommt die Funktion ohne Report sofort zurück.
Als wichtigste Besonderheit wird das IowKit2Write() überwacht, damit das Ein- und Ausschalten der Special Mode Function erkannt werden kann. Da einige Special Mode nicht gleichzeitig aktiv sein können, muss dann von einem zum anderen Teilgerät eine Konfigurationsänderung gemeldet werden. Der zugehörige IOWKIT2_CONFIGCHANGE_EVENT wird bei IowKit2Initialize() mit angegeben. Dieser Teil des APIs ist noch nicht ganz ausdefiniert. Bei den Special-Mode-Teilgeräten ist es nur die Nachricht das das Teilgerät nicht mehr aktiviert werden kann. Ein IowKit2Write() zum Einschalten des Special Mode wird also scheitern.
Die Parameter des IOWKIT2_CONFIGCHANGE_EVENT und die Struktur IOWKIT2_CONFIGINFO sind noch nicht in Stein gemeisselt. Ich nehme hier gerne Vorschläge für eine elegante Definition entgegen.
Falls sich jemand wundert warum es keine eigenständige Funktion zum Ein- und Ausschalten des Special Mode gibt, so ist die Antwort das damit die Flexibilität verloren geht. Der zugehörige Report kann noch mehrere Konfigurationsbytes enthalten. Da ist es doch einfacher und eleganter das Schreiben zu überwachen, da das sowieso erfolgen muss.
IowKit2ReadImmediate() bildet wohl eine eigene Gruppe. Es wird die Special Mode Function "Get Current Pin Status" abgebildet. Diese bildet kein eigenes virtuelles Teilgerät. Stattdessen kann man IowKit2ReadImmediate() nur auf das IO-Pin-Teilgerät aufrufen. Eine elegantere Möglichkeit diese Funktionalitat einzugliedern ist mir nicht eingefallen.
Die dritte Gruppe wird von den Informationsfunktionen gebildet.
IowKit2GetDeviceInfo() liefert alle Infos zum virtuellen Teilgerät und zum zugehörigen realen IO-Warrior. Diese Funktion wird logischerweise zuerst im IOWKIT2_DEVICECHANGE_EVENT aufgerufen, um herauszubekommen mit welchem Gerät man es zu tun hat. Ich habe davon abgesehen einen PIOWKIT2_DEVICEINFO-Parameter am IOWKIT2_DEVICECHANGE_EVENT-Callback vorzusehen. Es soll immer klar sein das die Daten ein Schnappschuss des aktuellen Gerätezustands sind.
Insbesondere trifft dies auf die IOWKIT2_DEVICEPARAMETERS-Teilstruktur zu. Diese wird nämlich auch zur Änderung der Parameter verwendet. Mit IowKit2SetDeviceParameters() kann man jederzeit Änderungen in der IOWKIT2_DEVICEPARAMETERS-Teilstruktur an das Gerät schreiben. Auf diese Weise ist ein Sortiment von API-Funktionen elegant auf eine reduziert worden.
IsUsed ist der wichtigste der Parameter. Mit dem Setzen dieses Parameters wird das Teilgerät in Benutzung genommen. Es ist nun belegt und kein anderes Programm kann es benutzen bis IsUsed wieder zurückgesetzt wird. Das Teilgerät bleibt sogar vorhanden wenn man den zugehörigen IO-Warrior aussteckt. Es wird dann zum Zombie-Gerät und hört auf zu funktionieren. IowKit2Write() wird immer versagen. IowKit2Read() und IowKit2ReadNonBlocking() versagen sobald keine gepufferten Reports mehr vorhanden sind. Die normale Vorgehensweise ist aber das man das Teilgerät im IOWKIT2_DEVICECHANGE_EVENT für das Ausstecken freigibt.
Mask ist nur für das IO-Pins-Teilgerät von Bedeutung. Man setzt in den Bytes des Reprots die Bits der IO-Pins, die man reservieren will. Die ReportID des Reports wird dazu missbraucht anzugeben ob die Makse gültig ist. Reserviert man Pins, die zu Special-Mode-Teilgeräten gehören, so werden diese unbenutzbar und das Teilgerät taucht nicht mehr auf.
Sind nicht alle IO-Pins reserviert, so kann ein anderes Programm sie belegen. Es werden also intern mehrere virtuelle Io-Pins-Teilgeräte verwaltet.
Dieser Teil des APIs (besonders im Zusammenspiel mit dem IOWKIT2_CONFIGCHANGE_EVENT) muss noch genauer durchgesprochen werden.
PendingReports meldet wieviele Reports gerade zum Lesen in der Warteschlange stehen. Setzt man hier Null ein, so werden alle Reports weggeworfen. Dies implementiert also einen Flush der Warteschlange. Andere Werte als Null werden ignoriert, da es sonst zu ungewollten Reportverlusten kommen könnte.
Zu ReadTimeout und WriteTimeout muss wohl nicht viel erklärt werden.
DataEvent ist ein weiterer Callback. Bevor ein Report in die Warteschlange eingefügt wird, wird er per IOWKIT2_DATA_EVENT-Callback an die Applikation gemeldet. Der Rückgabewert des Callbacks bestimmt ob der Report in die Warteschlange gelangt oder ob er bereits im Callback verarbeitet wurde. Mit diesem Mechanismus kann man die Reports in einer eigenen Funktion verarbeiten, nur einige Reports ausfiltern oder klassisch mit IowKit2Write/IowKit2Read-Paaren arbeiten.
Context kann beliebig belegt werden. Der Parameter Context von IowKit2Initialize() ist nicht mit diesem Element identisch. Der Parameter von IowKit2Initialize() wird an IOWKIT2_DEVICECHANGE_EVENT weitergereicht. Er lässt sich in der Applikation sehr gut für die Rückkehr in die Objektorientierung verwenden. "this" in C++ bzw. "Self" in Delphi ist ein Kandidat für den Parameter.
Jetzt möchte ich nochmals zu reger Diskussion aufrufen. Nichts ist in Stein gemeisselt. Wem ein besserer Ansatz einfällt, der wird gehört werden. Auch das Besserwissen an der mickrigsten Kleinigkeit wird gerne akzeptiert.
Die Implementation dieses Ansatzes ist schwierig genug. Es wird wohl auch unter Windows auf einen Daemon hinauslaufen, d. h. die DLL wird eine unsichtbare Applikation starten, die das Lesen und Verwalten der realen Geräte für die DLL übernimmt.