IPS Steuerung ect. mit ServerSocket und Register Variable

Tach, ich mal wieder.
Auf die Gefahr hin das es schon gepostet wurde, hier eine Anleitung zur Verwendung der ServerSockets in IPS

Da ich jetzt oft gelesen habe man möchte auf eigene HTTP anfragen reagieren hier eine 1:1 Lösung
Damit kann man POST, GETS, NOTIFYS und IPS Funktionen einfach auswerten und per Browsereingabe/Formular ect Werte in IPS ändern.

Step 1: Einen ServerSocket erstellen und als Port 9998 festlegen
Step 2: Eine RegisterVariable anlegen und als Übergeordnete Instance den soeben Erstellten ServerSocket auswählen
Step 3. Ein Script erstellen mit folgendem … mehr oder weniger langem Code


<?
if(!function_exists('Debug')){function Debug($data){if($_IPS['SENDER']=='Execute')echo $data.PHP_EOL;else IPS_LogMessage('ServerDataHandler:',$data);}}

if ($_IPS['SENDER'] == "RegisterVariable"){
    $Handler=new IPSServerDataHandler( $_IPS['INSTANCE'], $_IPS['VALUE']);

    $ok=$Handler->CheckData(function($event) {
		$data='Alles wird GUT...';
		switch($event['method']){
			case 'GET': break;
			case 'POST': break;
			case 'NOTIFY': break;
			case 'CALL'    :
				$FunctionName=$event['content']['name'];
				if(!function_exists($FunctionName)){
					$data.="oder auch nicht.
Unbekannte Funktion: $FunctionName
";
					break;
				}
				$Arguments=&$event['content']['arguments'];
				$result=null;
				switch(count($Arguments)){
					case 0: $result=$FunctionName(); break;
					case 1: $result=$FunctionName($Arguments[0]); break;
					case 2: $result=$FunctionName($Arguments[0],$Arguments[1]); break;
					case 3: $result=$FunctionName($Arguments[0],$Arguments[1],$Arguments[2]); break;
					case 4: $result=$FunctionName($Arguments[0],$Arguments[1],$Arguments[2],$Arguments[3]); break;
					case 5: $result=$FunctionName($Arguments[0],$Arguments[1],$Arguments[2],$Arguments[3],$Arguments[4]); break;
					// .... usw.
				}
				$e=error_get_last();
				if($result===false||$e)
					$data.="oder auch nicht....
Fehler beim Aufruf von: $FunctionName {$e['message']}
";
				else if(is_null($result)!==true)
					$data=json_encode($result);
				
				break;
		}
		return $data;
	});
}


class IPSServerDataHandler {
    private $INSTANCE_ID=0;
    private $CALLBACKURL='';
    private $LAST_EVENT=null;

    function __construct($InstanceId, $value){
        $this->INSTANCE_ID=$InstanceId;
        $this->DATA = RegVar_GetBuffer($InstanceId).$value;
        $this->DEBUG= $this->__read('DEBUG',0);
        if($this->DEBUG)Debug('GET :'.$this->DATA);
    }
    function CheckData($CallBack=null){
        $sendData=[];
        while($ev=$this->parseHeader()){
            $sendData=($CallBack)?$CallBack($ev):array('status'=>'200 OK','content'=>'');
            $this->__SendHttpResponse($sendData);
            $this->LAST_EVENT=$ev;
        }
        $this->__write('LAST_REQUEST',"{$this->LAST_EVENT['method']}: ".ArrayToStr($this->LAST_EVENT['content']));
        $this->__write('LAST_RESPONSE',ArrayToStr($sendData,0,''));
        RegVar_SetBuffer($this->INSTANCE_ID, $this->DATA);
        return ($CallBack)?(bool)$this->LAST_EVENT:$this->LAST_EVENT;
    }
    public function LastEvent(){
        return $this->LAST_EVENT;
    }
    private function parseHeader(){
		@list($head, $body) = explode("

", $this->DATA, 2);
		if(!$head)return false;
		$hsize=strlen($head);
		$head=explode("
",$head);
		list($r['method'], $r['data'],$r['type']) = explode(' ', array_shift($head), 3);
		if (($r['method']=='GET'||$r['method']=='POST'||$r['method']=='NOTIFY')===false)return false;
		if($r['data'] && $r['data'][0]=='/')$r['data']=substr($r['data'],1);
		$len=0;
		foreach($head as $b){
			list($k,$v) = explode(':',$b,2);
			$k=strtoupper(trim($k));
			if($k=='CONTENT-LENGTH')$len=(int)$v;
			$r['head'][$k]=trim($v);
		}
		$r['compled']=($len)?(strlen($body)==$len):1;
		if($r['compled']===false)return false;
		$this->DATA=trim(substr($this->DATA,$hsize+$len));
		if($r['method']=='NOTIFY')return $r;
		if($r['data']&&($r['data'][0]=='?'||$r['data'][0]=='&'))
			$pa=explode('&',substr($data,1));
		elseif($r['method']=='POST')
			$pa=explode('&',$r['body']);
		elseif($r['method']=='GET'){
			if(preg_match('/(.*)\((.*)\)/',$r['data'],$matches)){
				array_shift($matches);
				$r['method']='CALL';
				$da['name']=array_shift($matches);
				$aa=array_shift($matches);
				if($aa!=''){
					$tmp=explode(',',$aa);
					for($j=0;$j<count($tmp);$j++){
						$v=$tmp[$j];
						if(is_bool($v)===true)
							$v=(bool)$v;
						else if(is_numeric($v)===true){
							if(is_double($v)===true)$v=(double)$v;
							else $v=(integer)$v;
						}
						$tmp[$j]=$v;
					}
					$da['arguments']=$tmp;
				}else $da['arguments']=[];
				$body=$da;
			}
		}
		$r['content']=$body;
		return $r;
    }
    private function __SendHttpResponse($Data=null){
        $id=@IPS_GetInstance($this->INSTANCE_ID)['ConnectionID'];
        if(!$id)return false;
        if(!$Data)$Data=array('status'=>'200 OK');
        if(!is_array($Data))$Data=array('content'=>$Data);
        if(!isSet($Data['status']))$Data['status']='200 OK';
        if(!isSet($Data['content']))$Data['content']='';
        $res[]="HTTP/1.1 {$Data['status']}";
        if($Data['content']){
            if(!isSet($Data['size']))$Data['size']=strlen($Data['content']);
            if(!isSet($Data['type']))$Data['type']='text/plain';
            $res[]="CONTENT-TYPE: {$Data['type']}";
            $res[]="CONTENT-LENGTH: {$Data['size']}";
            $res[]="";
            $res[]=$Data['content'];
        }
        $content=implode("
",$res).PHP_EOL;
        $this->__write('LAST_STATE',$Data['status']);
        if($this->DEBUG)Debug('SEND: '.$content);
        return SSCK_SendText($id, $content);
    }
    private function __write($VarIdent, $VarData, $VarType=3){
        $id=@IPS_GetObjectIdByIdent($VarIdent,$this->INSTANCE_ID);
        if(!$id){$id=IPS_CreateVariable($VarType);IPS_SetParent($id,$this->INSTANCE_ID);IPS_SetIdent($id,$VarIdent);IPS_SetName($id,$VarIdent);}
        SetValue($id,$VarData);
    }
    private function __read($VarIdent, $VarType=3){
        $id=@IPS_GetObjectIdByIdent($VarIdent,$this->INSTANCE_ID);
        if(!$id){$id=IPS_CreateVariable($VarType);IPS_SetParent($id,$this->INSTANCE_ID);IPS_SetIdent($id,$VarIdent);IPS_SetName($id,$VarIdent);}
        return GetValue($id);
    }

}
function ArrayToStr($a,$step=0, $cr="
"){
    if(!$a)return 'null';else if(!is_array($a))return $a;
    $r='';$sp=str_repeat(' ',$step);
    foreach($a as $k=>$v)$r.="$sp'$k'=".(is_array($v)?"[$cr{$sp}".ArrayToStr($v,$step+2)."$sp$sp]":"$v")."$cr";
    return $r;
}
?>

Step.4 : In den Eigenschaften der soeben erstellten RegisterVariable unter Target das neu erstellte Script zuweisen.
Fertig.

Im Browser, auf dem IPS Server, (wenn du es lokal probierts und dein IPS Server nicht auf dem gleichen Rechner läuft auf dem du Surfst, musst du ggfls den Port 9998 im Firewall auf dem IPS Server freigeben) dann folgendes eingeben:

Beispiele:
http://localhost:9998/IPS_GetFunctionList(0)
http://localhost:9998/SetValue(1234,‚Hallo‘)
http://localhost:9998/?a=1&b=2&c=3

In der IPS Console kannst du den letzten Status unter den Systemvariablen der RegisterVariable sehen.

Have fun
cu Xaver

Das ist echt der Hammer was Du die letzte Zeit so an coolem Zeug raushaust! Vielen Dank!

Weiss zwar noch nicht, wofür ich das brauche, ist aber mal eingerichtet und läuft … danke :wink:

Gruß
Bruno

Super, läuft perfekt.

Nur beim allerersten Aufruf von
http://<IP>:<PORT>/IPS_GetFunctionList(0)
gibt es eine Fehlermeldung:

Alles wird GUT...
oder auch nicht.... :(
Fehler beim Aufruf von: IPS_GetFunctionList
Objekt mit Ident DEBUG wurde nicht gefunden

Liegt wohl daran, dass die DEBUG Variable noch nicht angelegt ist …

Viele Grüße

Burkhard

Danke für die Rückmeldungen, da hat man doch gleich das Gefühl die Arbeit nicht umsonst gemacht zu haben :slight_smile:
cu
Xaver

Kann es sein, dass dieses nur unter Windows läuft, denn unter Ubuntu beim Aufruf der URL:
http://192.168.1.22:9998/IPS_GetFunctionList(0)
kommt immer nur verbinden und auch keine Fehlermeldung.
Eine Firewall ist nicht aktiv.

VG
Ralf

Auch von mir nen Danke! Genau das was ich in diesem Moment gebraucht habe um die Daten von meiner Wetterstation zu übergeben.

Wie IPS unter Linux reagiert kann ich leider nicht sagen. Es gibt da im Moment glaube ich noch einschränkungen.

Dasmit dem Browser hängen passiert immer dann, wenn der Browser keine ok Antwort bekommt. Dann wartet und wartet und wartet… er mit der Sanduhr.
Du kannst in der RegisterVariable mal schauen was unter der Lasche Debug angezeigt wird wenn du eine abfrage machst.

cu
Xaver

Gern geschehen, habe im Forum schon gelesen das einige einfach so was simples brauchen. :wink:

cu
Xaver

Hallo,
ich versuche mit deinem Skript ein Homematic-Gerät anzusteuern, doch leider scheitere ich…
Bislang habe ich diesen Link probiert: http://home:9998/HM_WriteValueBoolean(41767,STATE,true)
Der Browser (Chrome) spuckte dann folgendes aus:

Alles wird GUT…
oder auch nicht… :frowning:
Fehler beim Aufruf von: HM_WriteValueBoolean
Wrong parameter type for HM_WriteValueBoolean()

Was muss ich denn nun im Browser eingeben um Dat Dingen zu schalten?

Gruß Maik

Ich versuche gerade mit Deinem Skript die NOTIFY Events von meinen Peaq Lautsprechern zu verarbeiten. Der Header wird empfangen aber der Content ist leider leer:


Notify:Array
(
    [method] => NOTIFY
    [head] => Array
        (
            [HOST] => 192.168.11.23
            [NT] => upnp
            [NTS] => upnp
            [SID] => uuid
            [SEQ] => 0
            [CONTENT-TYPE] => text/xml; charset="utf-8"
            [CONNECTION] => Keep-Alive
            [CONTENT-LENGTH] => 1581
        )


    [content] =>  
)


Zudem bekomme ich immer eine Fehlermeldung:


Error: Notice: Undefined variable: sendData
   Error in Script C:\IP-Symcon\scripts\caskeid.serversocket.ips.php on Line 70
  134 in scripts\IPSLibrary\app\core\IPSLogger\IPSLogger.inc.php (call IPSLogger_Out)
   44 in scripts\IPSLibrary\app\core\IPSLogger\IPSLogger_PhpErrorHandler.inc.php (call IPSLogger_Err)
   70 in scripts\caskeid.serversocket.ips.php (call IPSLogger_PhpErrorHandler)
   44 in scripts\caskeid.serversocket.ips.php (call CheckData)

Was mache ich da wohl falsch?

Hab bei meinem zweiten Socket das gleiche Problem.
Die Post Daten von Geofancy stehen bei mir auch im head anstatt im content.

Bei mir scheint es die Firewall zu sein, der Portforward scheint die Daten zu verändern. Direkt lokal aufgerufen stehen die Daten wieder im content, wie es sich gehört.

Edit:
Jap, das ist es.
Firewall Rule auf Pure NAT geändert, und dann gehts auch von Extern.

Ok, auf das Parsing meines Headers möchte ich nun nicht die hand ins feuer legen.
Normalerweise , so hab ich die Erfahrung gemacht, folgen nach den eigentlich die Header angaben, sprich content-lenght ect , dann getrennt duch eine Leerzeile, die eigenlichen content Daten die genau so lang sein müssen wie in content-lenght angegeben .Stimmt die erkannte länge beim parsen, aus irgend welchen gründen nicht, dann wird der event nicht erkannt und weiter verarbeitet.

Wenn ich deinen Header hätte könnte ich das nachvollziehen.

in der while Schleife, in der Function CheckData, kann man den Inhalt von ev in eine log daei schreiben und dann sehen was genau reinkommt, bzw. was erkannt wird.

cu
xaver

Bei mir ists definitiv die Firewall die da etwas manipuliert, aber da ich die Daten ohnehin durch einen Reverse Proxy pumpe um eine SSL Verbindung nach außen zu haben ists nicht so schlimm. Der Reverse Proxy manipuliert den Header dahingehend interessanterweise nicht.

Keine Ahnung was die pfSense da treibt…

Zum einen hab ich oben was geschrieben :wink: den anderen Fehler hab ich im demo script behoben. Danke für den Hinweis. cu Xaver

So schaut der Header aus wenn er korrekt durch den Reverse Proxy kommt:


02.02.2015 02:28:16.023 | 27549 | MESSAGE | RegisterVariable     | RegVar Handler result:  POST / HTTP/1.1
Host: xxxxx.xx:xxxxx
Content-Type: application/x-www-form-urlencoded
Accept: */*
User-Agent: Geofency/41 CFNetwork/711.1.16 Darwin/14.0.0
Accept-Language: de-de
Accept-Encoding: gzip, deflate
X-Forwarded-For: xx.xxx.xxx.xxx
X-Forwarded-Host: xxxxx.xx:xxxx
X-Forwarded-Server: webproxy.xxxx.xxx
Connection: Keep-Alive
Content-Length: 196

device=5D2512C2-539A-4C15-A0FA-1FE74C753397&radius=100&longitude=8.731373306441032&id=A4BD2636-BF1E-4539-B572-5D10DB44E444&date=2015-02-02T01:28:16Z&latitude=40.02647349899189&entry=1&name=Zuhause

Und so wenn es direkt aus dem Port Forward kommt:


02.02.2015 02:36:45.083 | 27549 | MESSAGE | RegisterVariable     | RegVar Handler result:  device=5D2512C2-539A-4C15-A0FA-1FE74C753397&radius=100&longitude=8.731373306441032&id=A4BD2636-BF1E-4539-B572-5D10DB44E444&date=2015-02-02T01:36:23Z&latitude=40.02647349899189&entry=1&name=ZuhausePOST / HTTP/1.1
Host: xxxx.xx:xxxx
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Accept: */*
User-Agent: Geofency/41 CFNetwork/711.1.16 Darwin/14.0.0
Content-Length: 196
Accept-Language: de-de
Accept-Encoding: gzip, deflate

Aus dem Meldungen-Tab kann man leider nichts rauskopieren, daher als Screenshot:

Für mich sieht das so aus als ob Header und Content „falsch herum“ zusammengebaut wurden. Erst kommt der Inhalt und dann der Kopf. In der Debug-Ansicht der RegisterVar sieht man aber das die Daten in der korrekten Reihenfolge dort eintreffen:

@Titus: Bzgl. Meldungen aus „Meldungen-Fenster“ rauskopieren :slight_smile: >> IPS-Console / in der „Meldungen“-Ansicht die Meldungen mit rechte Maustaste kopieren
…workaround = Meldungen aus Log-Datei rauskopieren… Gerne darfst du aber meinen Funktionswunsch unterstützen :slight_smile:

Grüße,
Chris

Ok , ich werde das morgen mal checken … :wink:

@Titus … auch das werde ich morgen damit abklären.

cu Xaver

So ich hab mal einiges überarbeitet und denke es sollte so funktionieren. Ist dafür auch etwas schlanker geworden;). Der Code steht auf der 1. Seite,

cu

Xaver