Remote Keyboard Server

Im IPS 2.0 gibt es ja leider keine Möglichkeit, Eingaben auf der Computertastatur auszuwerten. Darum habe ich einen kleinen „Remote Keyboard Server“ geschrieben, der das erledigt.

Das Prinzip basiert auf einer Socket-Kommunikation mittels Telnet. Der Vorteil ist, dass man die Tastatur von irgendeinem PC (nicht nur vom IPS-Server) übertragen kann. Das passt besser zur neuen Client-Server Architektur als eine lokale Tastaturabfrage am IPS-Server.

Als kleines Nikolausgeschenk :cool: habe ich das Skript hier eingestellt. Einfach in das IPS-Verzeichnis kopieren und manuell ausführen. Es installiert selbst die benötigten Instanzen und Variablen. Für weitere Erklärungen schaut in die Kommentare im Code.

Getestet habe ich es mit Windows XP Telnet Client. Hoffentlich funktioniert es bei euch. Garantie übernehme ich nicht :wink:

Als Rückmeldung kriegt man übrigens auf dem Telnet-Client ein IPS-Prompt und ein Echo der gedrückten Taste. Da das IPS Server Socket Modul erst beim ersten Tastendruck das Skript triggert (ein Triggern beim Öffnen der Verbindung ist noch auf meinem Wunschzettel an paresy), kommt das Prompt erst nach der ersten Taste.

Das Skript war zu groß, um hier eingefügt zu werden. Darum findet ihr es zwei Beiträge weiter unten.
Viele Grüße
<ursprünglichen Anhang habe ich wieder entfernt>

Hier können normale Benutzer wie wir keine Anhänge runterladen.

Wollte das Skript ursprünglich im Klartext einfügen, aber die Maximallänge des Postings lag mit 10607 Zeichen knapp über der maximal erlaubten Länge von 10000 Zeichen:(

Ich hab es ein wenig gekürzt, nun passt es gerade so.


<?
/* Remote Keyboard-Server von docM
	Dieses Skript ermöglicht die Auswertung einer PC-Tastatur in IPS V2.0
	Hierzu wird Telnet verwendet. Der Remote Keyboard Server imitiert
	einen Telnet Server (Port 23).
	
	Auf dem PC, der zur Steuerung verwendet werden soll, startet man einen
	Telnet-Client im ANSI-Modus und verbindet ihn mit dem IPS Server. Unter
	Windows-XP geht das einfach mit dem Befehl

	telnet <ip-Adresse oder Computername des IPS-Servers>
	
	in der Kommandozeile.
	
	Wenn man jetzt im Telnet-Client eine Taste drückt, so wird das zugehörige
	Zeichen an den Keyboard-Server geschickt und daraufhin automatisch
	dieses Skript hier aktiviert.
	
	Ganz am Ende des Skripts sind zwei Funktionen, die man auf seine eigenen
	Bedürfnisse anpassen muss.
	pressedNormalKey() wird beim Druck auf eine normale Taste,
	pressedSpecialKey() beim Druck auf eine Sondertaste aufgerufen.
	Hier kann man nun auf die Zeichen reagieren.
	
	Die Installation des Servers geschieht wie folgt. Einfach dieses Skript in
	den Objektbaum kopieren und manuell ausführen.
	Nicht wundern, wenn es anschließend nicht mehr an der alten Position im
	Objektbaum zu finden ist.
	Bei der Installation passiert folgendes
	- Es wird unter I/O Instanzen ein Server Socket mit Namen
	 'Remote Keyboard Server' angelegt
	- Darunter wird eine Register-Variable mit Namen 'Weiterleitung' erzeugt.
	- Dieses Skript wird automatisch im Objektbaum unter den Keyboard Server
	  verschoben und erhält den Namen 'Verarbeitung'
	- Es wird unterhalb des Keyboard Servers noch eine String Variable
	  'letzte ASCII-Codes' angelegt. Sie zeigt (in Hex) die Codes der letzten
	  Taste an.
*/

function GetModuleId($moduleName)
// Hilfsfunktion, benötigt beim automatischen Anlegen der Instanzen
{
	foreach (IPS_GetModuleList() as $moduleId)
	{
		$module = IPS_GetModule($moduleId);
		if ($module['ModuleName']==$moduleName) return $moduleId;
	}
	return '';
}

function echoTerminal($text)
{
// Ausgabe eines Textes auf dem Telnet-Client
	global $IPS_SELF;
	$scriptObject =IPS_GetObject($IPS_SELF);
	SSCK_SendText($scriptObject['ParentID'],$text);
}

function parseKeys($keys)
{
// Diese Funktion wird immer dann aufgerufen, wenn der Remote Keyboard Server
// Zeichen empfangen hat.
// Sondertasten
	$ansispecialkeys = array(
		'F1'       => chr(0x1B).chr(0x4F).chr(0x50),
		'F2'       => chr(0x1B).chr(0x4F).chr(0x51),
		'F3'       => chr(0x1B).chr(0x4F).chr(0x52),
		'F4'       => chr(0x1B).chr(0x4F).chr(0x53),
		'F5'       => chr(0x1B).chr(0x5B).chr(0x31).chr(0x35).chr(0x7E),
		'F6'       => chr(0x1B).chr(0x5B).chr(0x31).chr(0x37).chr(0x7E),
		'F7'       => chr(0x1B).chr(0x5B).chr(0x31).chr(0x38).chr(0x7E),
		'F8'       => chr(0x1B).chr(0x5B).chr(0x31).chr(0x39).chr(0x7E),
		'F9'       => chr(0x1B).chr(0x5B).chr(0x32).chr(0x30).chr(0x7E),
		'F10'      => chr(0x1B).chr(0x5B).chr(0x32).chr(0x31).chr(0x7E),
		'F11'      => chr(0x1B).chr(0x5B).chr(0x32).chr(0x33).chr(0x7E),
		'F12'      => chr(0x1B).chr(0x5B).chr(0x32).chr(0x34).chr(0x7E),
		'UP'       => chr(0x1B).chr(0x5B).chr(0x41),
		'DOWN'     => chr(0x1B).chr(0x5B).chr(0x42),
		'RIGHT'    => chr(0x1B).chr(0x5B).chr(0x43),
		'LEFT'     => chr(0x1B).chr(0x5B).chr(0x44),
		'PG-UP'    => chr(0x1B).chr(0x5B).chr(0x35).chr(0x7E),
		'PG-DOWN'  => chr(0x1B).chr(0x5B).chr(0x36).chr(0x7E),
		'POS1'     => chr(0x1B).chr(0x5B).chr(0x31).chr(0x7E),
		'ENDE'     => chr(0x1B).chr(0x5B).chr(0x34).chr(0x7E),
		'ENTER'    => chr(0x0D).chr(0x0A),
		'BS'       => chr(0x08),
		'ENTF'     => chr(0x7F),
		'STRG-A'   => chr(0x01),
		'STRG-B'   => chr(0x02),
		'STRG-C'   => chr(0x03),
		'STRG-D'   => chr(0x04),
		'STRG-E'   => chr(0x05),
		'STRG-F'   => chr(0x06),
		'STRG-G'   => chr(0x07),
		'STRG-H'   => chr(0x08),
		'STRG-I'   => chr(0x09),
		'STRG-J'   => chr(0x0A),
		'STRG-K'   => chr(0x0B),
		'STRG-L'   => chr(0x0C),
		'STRG-N'   => chr(0x0E),
		'STRG-O'   => chr(0x0F),
		'STRG-P'   => chr(0x10),
		'STRG-Q'   => chr(0x11),
		'STRG-R'   => chr(0x12),
		'STRG-S'   => chr(0x13),
		'STRG-T'   => chr(0x14),
		'STRG-U'   => chr(0x15),
		'STRG-V'   => chr(0x16),
		'STRG-W'   => chr(0x17),
		'STRG-X'   => chr(0x18),
		'STRG-Y'   => chr(0x19),
		'STRG-Z'   => chr(0x1A),
		'ESC'      => chr(0x1B)
// wichtig, dass der "nackte" ESC am Ende der Liste steht. Sonst werden beim
// Parsen die ESC-Sequenzen nicht erkannt.
	);
	for ($keyslen = strlen($keys);$keyslen>0;$keyslen-=$cutoff)
	{
		$cutoff=1;
// Telnet Kommandos unterdrücken. Sie beginnen immer mit IAC=0xFF und sind 3
// Zeichen lang.
		if (($keys[0]==chr(0xFF)) && ($keyslen>2))
		{
			$cutoff=3;
		}
// Sondertastenverarbeitung
		elseif (($keys[0]<=chr(0x21)) || ($keys[0]==chr(0x7F)))
		{
			foreach($ansispecialkeys as $code =>$sequence)
			{
				$seqlen = strlen($sequence);
				if ($seqlen>$keyslen) continue;
				if(substr_compare($keys,$sequence,0,$seqlen)==0)
				{
// Benutzerfunktion aufrufen
					pressedSpecialKey($code);
// Echo auf dem Terminal
					echoTerminal($code.chr(0x0D).chr(0x0A).chr(0xFF).chr(0xFB).chr(0x01).'IPS>');
					$cutoff=$seqlen;
					break;
				}
			}
		}
		else
		{
			pressedNormalKey($keys[0]);
			echoTerminal($keys[0].chr(0x0D).chr(0x0A).chr(0xFF).chr(0xFB).chr(0x01).'IPS>');
		}
		$keys=substr($keys,$cutoff);
	}
}

// Hauptprogramm startet hier!
if ($IPS_SENDER == 'RegisterVariable')
// Der Keyboard-Server hat Zeichen empfangen
{
	$scriptObject =IPS_GetObject($IPS_SELF);
	$keys= $IPS_VALUE;

// Schreibe die letzten ASCII-Codes in die Variable
	$result = "";
	for($i=0;$i<strlen($keys);$i++)
	{
		$result.= sprintf('0x%02X ',ord($keys[$i])); // HEX format
	}
	$asciiAusgabe = IPS_GetVariableIDByName('Letzte ASCII-Codes',$scriptObject['ParentID']);
	SetValueString($asciiAusgabe,$result);

// Wenn es was zu tun gibt, rufe die Benutzerfunktion zur Auswertung auf
 	f ($keys != '') ParseKeys($keys);
}
elseif ($IPS_SENDER=='TimerEvent')
// Dies ist der zweite Durchlauf der Installation.
// Zwei Durchläufe sind erforderlich, damit IPS seine ScriptEngine aktualisieren
// kann. Sonst sind die Funktionen SSCK_ und RegVar_ möglicherweise nicht
// definiert und das Skript endet im Fatal Error.
{
// lösche den Timer, er diente nur zum Zweck diesen Durchlauf zu triggern.
	IPS_SetScriptTimer($IPS_SELF,0);
	$timer = IPS_GetEventIDByName('ScriptTimer',$IPS_SELF);
	IPS_DeleteEvent($timer);
	
	$scriptObject =IPS_GetObject($IPS_SELF);

// konfiguriere den Server Socket
	$socketServer = $scriptObject['ParentID'];
 	SSCK_SetPort($socketServer,23); // default Telnet Port
	SSCK_SetLimit($socketServer,1); // maximal 1 Client gleichzeitig
	SSCK_SetOpen($socketServer,true); // Port öffnen
	IPS_ApplyChanges($socketServer);

// konfiguriere die Register Variable
	$registerVariable = IPS_GetInstanceIDByName('Weiterleitung',$socketServer);
	RegVar_SetRXEnabled($registerVariable,true);
	IPS_ConnectInstance($registerVariable,$socketServer);
	RegVar_SetRXTarget($registerVariable,1);
	RegVar_SetRXObjectID($registerVariable,$IPS_SELF);
	IPS_ApplyChanges($registerVariable);

// fertig
	echo 'Installation erfolgreich';
}
else
{
// interaktives Ausführen: Erster Durchlauf der Installation.
	$scriptObject =IPS_GetObject($IPS_SELF);

// prüfe, ob wir schon installiert sind
	$parentObject = IPS_GetObject($scriptObject['ParentID']);
	if ($parentObject['ObjectType']==1)
	{
		$parentObject = IPS_GetInstance($scriptObject['ParentID']);
		if ($parentObject['ModuleInfo']['ModuleName']=='Server Socket')
		{
			echo 'Skript ist bereits installiert.';
			return;
		}
	}
// erzeuge den Server Socket
	$socketServer = IPS_CreateInstance(GetModuleId("Server Socket"));
	IPS_SetName($socketServer,'Remote Keyboard Server');

// erzeuge die Register Variable
	$registerVariable = IPS_CreateInstance(GetModuleId("Register Variable"));
	IPS_SetName($registerVariable,'Weiterleitung');
	IPS_SetParent($registerVariable,$socketServer);

// benenne mich selbst um und verschiebe mich im Objektbaum
	IPS_SetParent($IPS_SELF,$socketServer);
	IPS_SetName($IPS_SELF,'Verarbeitung');

// erzeuge die Variable zur Ausgabe der ASCII-Codes
	$asciiAusgabe = IPS_CreateVariable(3);
	IPS_SetName($asciiAusgabe,'Letzte ASCII-Codes');
	IPS_SetParent($asciiAusgabe, $socketServer);
// starte einen Timer, der dieses Skript nach einer Sekunde für den
// zweiten Durlauf neu startet. Das ermöglicht IPS seine ScriptEngine zu
// aktualisieren.
	echo 'starte 2. Durchlauf';
	IPS_SetScriptTimer($IPS_SELF,1);
}

function pressedSpecialKey($key)
{
// hier festlegen, was beim Druck auf eine Sondertaste geschehen soll
	switch ($key)
	{
		case 'ENTER':
			echo ('Es wurde "ENTER" gedrückt.');
			break;
		case 'F5':
			echo ('Es wurde "F5" gedrückt.');
			break;
		case 'STRG-B':
			echo ('Und jetzt "STRG-B".');
// usw. nach Belieben
	}
}

function pressedNormalKey($key)
{
// hier festlegen, was beim Druck auf eine normale Taste geschehen soll
	switch ($key)
	{
		case 'a':
			echo ('Es wurde das "A" gedrückt.');
			break;
		case 'A':
			echo ('Es wurde "SHIFT-A" gedrückt.');
			break;
// usw. nach Belieben
	}
}
?>

Cool das Nikolaus Geschenk habe ich gleich getestet … wie wars anders zu erwarten? … es FUNKTIONIERT…

Danke

Hallo docM,

was mich interessieren würde…

… welche Anwendungen möchtest Du damit realisieren.

Die Möglichkeiten sind ja riesig. Von einer Fernbedienung via Taster, egal ob lokal oder remote, bis zu weiteren Webanwendungen die eine Telnetsession steuern.

Kannst Du uns mitteilen was Du selbst damit vor hast.

Also, motiviert wurde ich durch die Diskussion hier. Da dachte ich mir, es müsste über Telnet eigentlich ganz einfach gehen. Der Kern des Skripts war nach einer Stunde fertig. Aber was es dann wirklich aufwändig gemacht hat, waren Wünsche wie

  • automatische Erzeugung der Instanzen
  • Behandlung von Sondertasten
  • Echo auf dem Telnet-Client steuern
    also wie so oft, das ganze drum herum für den Komfort.

Ich verwende es erstmal ganz geradeaus zur Steuerung per Tastatur. Vielleicht könte es auch ein Weg sein, um ein einfaches WLAN-Mobilgerät als „Fernbedienung“ einzubinden. Aber das geht natürlich auch über das Webfrontend.

Die Integration von IPS mit anderen Systemen interessiert mich sehr. So will ich z.B. noch eine Überwachungskamera installieren, deren Aufzeichnung automatisch startet, wenn mein FS20-Bewegungsmelder anschlägt. Für solche Sachen, dürfte aber das SOAP Interface besser sein.

Viele Grüße und noch viel Spaß mit dem Remote Keyboard Server.

Hallo,

eine Frage hierzu:

Ich habe einen Client mit Touchscreen und darauf den Designer laufen, jetzt möchte ich mit der Tastatur an diesem Client Befehle zu IPS schicken, geht das auch, wenn ich das Telnet-Fenster nicht aktiviert habe bzw. wenn der Designer aktiv ist und das Telnet-Fenster nur im Hintergrund läuft?

Wenn das geht wäre das wirklich klasse!

Wenn nicht, gibt es eine alternative?

Grüße

ASICS

Hallo,
hab mal das Script von docM für IPS2.2 angepasst

tgusi74

REMOTE_KEYBOARD_SERVER.zip (3.33 KB)