Access CCU (Actors & Sensors) via php

Nutzung von XML RPC, Remote Script, JSON RPC, XMLAPI

Moderator: Co-Administratoren

Benutzeravatar
a.krypthul
Beiträge: 41
Registriert: 27.01.2008, 13:51
Danksagung erhalten: 1 Mal

Access CCU (Actors & Sensors) via php

Beitrag von a.krypthul » 07.03.2008, 22:29

Hey all,

today I´d like to describe a method to access HM actors & sensor from outside (PC or Linx ...) via PHP-script without installing additional software on the CCU.

First of all a little bit about the HomeMatic basic-communication to understand the solution:

Internally HM-software uses " a kind of" rpc-calls (remote procedure calls) to communicate between

- the "logic-processor (system)"
- the "wired controller"
- the "wireless controller"

these processes comunicate via the tpc-ports 1999-2002.

The process-communication is based on a open-source project "xmlrpc++" XML-Remote-Procedure-Calls.
To avoid the "overhead" of XML-processing, EQ3 has modified this project (and following the open-source license - put into it´s open-source part of the HomeMatic :-) and added a relatively simple "binary" interface to be used by the HomeMatic processes.

The workflow:

1. create a binary package with the commands (request)
2. send it via socket-communication to the system port 2001
3. receive the answer (binary package)
4. analyze the answer

There is a lot of functionality behind these rpc-calls (all you can do with the Web-UI), but this example demonstrates only a small part :

- setting actors (e.g. switches on/off, timer, inhibit, toggle )
- get the state of actors (on or off ?)
- get the state of sensors (e.g. weathersensor - temperature/humidity)

the base part is a php-class which is used to create and analyze the request/answers. hmcontroller.php

Code: Alles auswählen

<?php

class Class_hmController 
{
 public  $rpc_method;
 public  $rpc_argct = 0;
 public  $arg_data = array();
 public  $arg_type = array();	

 public  $crlf="\r\n";

 private $answer;
 private $blocksize = 128; 
 private $request;	
 private $requestlen;

public function __construct($ip = "", $port = 0) 
	{
   $this->ip = $ip;
   $this->port = $port;
		}

/************************************************************************************\
 Add a rpc parameter
\************************************************************************************/
public function AddParam ($value, $type)
{
 if ($value == "") return;	
 $this->arg_type[$this->rpc_argct] = $type;
 $this->arg_data[$this->rpc_argct] = $value;	
 $this->rpc_argct++;
}

/************************************************************************************\
     Analyze the binary answer of the HM
\************************************************************************************/
public function AnalyzeRequest()
{
 echo $this->crlf."Answer:".$this->crlf;
 if (strlen($this->answer) >= 8) // minimum size of a valid answer (empty)
   if (ord(substr($this->answer,3,1)) == 0x01) 
   {
    $arr = unpack("Nint", substr($this->answer,4,4));	
   	$length = $arr["int"];
   	$ct = 0;
   	echo "Length:".$length.$this->crlf;
   	while ($length > $ct)
   	  {
       $arr = unpack("Nint", substr($this->answer,8+$ct,4));	   	  	
       $type = $arr["int"];
       $ct+=4;
       switch ($type)
       {
         case 1: //4byte integer
            $arr = unpack("Nint", substr($this->answer,8+$ct,4));
            $value = $arr["int"];	   	  	
            $ct+=4;
            break;	       	
         case 2: //boolean
            $arr = unpack("Cint", substr($this->answer,8+$ct,1));
            $value = $arr["int"];	   	  	
            $ct++;
            break;	       	
         case 3: //string
            $arr = unpack("Nint", substr($this->answer,8+$ct,4));
            $slen = $arr["int"];	   	  	
            $value = substr($this->answer,8+$ct+1,$slen);
            $ct += 4+$slen;
            break;	
         case 4: //double value
            $arr = unpack("N2int", substr($this->answer,8+$ct,8));
            $mantissa = $arr["int1"];	   	  	
            $exponent = $arr["int2"];
            $value = round (((double)$mantissa / (double)0x40000000) * (double)(Pow(2, $exponent)),2);
            $ct+=8;
         break;	
       }
      if ($type == 3 && $value == "")
         echo "Value: (empty = OK)!".$this->crlf;
      else
         echo "Value: ".$value.$this->crlf;
   	  }
   } 	
   else
   {
   	echo "Error".$this->crlf;
   }
}

/************************************************************************************\
 print the request to screen
\*************************************************************************************/
public function DumpRequest()
{
	echo "Request: \"".$this->rpc_method."\"".$this->crlf;
	for ($a=0; $a< $this->rpc_argct; $a++)
	  echo "   Param[".$a,"]=\"".$this->arg_data[$a]."\" Type(".$this->arg_type[$a].")".$this->crlf; 
}

/************************************************************************************\
 Execute the request and get back the binary answer
\************************************************************************************/
public function ExecuteRequest ()
  {
   // method
   $this->requestlen = strlen ($this->rpc_method)+8;
   $this->request = pack("N", strlen ($this->rpc_method)).$this->rpc_method.pack("N",$this->rpc_argct); 	   

   // the arguments of the request
   for ($a=0; $a<$this->rpc_argct; $a++)	
     {
      $this->request .= pack("N",$this->arg_type[$a]); 	
      $this->requestlen += 4;  	
      switch($this->arg_type[$a])
      {
       case 3 : //string
           $this->request .= pack("N",strlen($this->arg_data[$a])).$this->arg_data[$a]; 	
           $this->requestlen += 4 + strlen($this->arg_data[$a]);  	
           break;
       case 2 : //boolean (1byte) all values different from zero are accepted as "true"
           $this->request .= chr($this->arg_data[$a]); 	
           $this->requestlen ++;  	
           break;   
       case 4 : //double 
           $Exponent = 0;
           $tmp = Abs($this->arg_data[$a]);
           while ($tmp >= 2 )
           {
            $tmp = Abs($this->arg_data[$a])/ Pow(2, $Exponent++);
            echo "t:".$tmp."<br>";
           }
           $tmp = $this->arg_data[$a] / Pow(2, $Exponent); 
           $Mantissa = ((double)$tmp * 0x40000000);
           $this->request .= pack("NN",$Mantissa, $Exponent); 	
           $this->requestlen +=8;  	
           break;        
      }
     }

   // create the complete request (header + requestpart)
   $this->request="Bin".chr(0).pack("N",$this->requestlen).$this->request;
   
   // send the reqest and get the answer
   $fs = fsockopen ($this->ip, $this->port, $errno, $errstr, 3);
   stream_set_timeout($fs, 1);

    if ($fs != 0)
  	{   
	   fwrite ($fs, $this->request, strlen($this->request));
	   $this->answer = "";
	   do 
      {
        $block = fread($fs,$this->blocksize);
        $this->answer .= $block;
      }
      while (strlen($block) == $this->blocksize); 
     fclose ($fs);
    }

   return (strlen($this->answer) > 0); // execute is done return true if there is an answer
  } 
}
?>
the second part is a php-cgi-wrapper to use this php-class (simple example) hmcontrol.php

Code: Alles auswählen

<?php
include "hmcontroller.php";

$hm = new Class_hmController("10.9.1.2", 2001); // change this to your HM-address
$hm->crlf="<br>";
// set the classes parameter

$hm->rpc_method = $HTTP_GET_VARS["method"]; 
$hm->AddParam($HTTP_GET_VARS["address"],3);

switch ($HTTP_GET_VARS["method"])
  { 
  case "getValue":
     $hm->AddParam($HTTP_GET_VARS["what"],3);
   break;
  case "setValue": 
     $hm->AddParam($HTTP_GET_VARS["what"],3);
     if (    $HTTP_GET_VARS["what"] == STATE 
          || $HTTP_GET_VARS["what"] == INSTALL_TEST
          || $HTTP_GET_VARS["what"] == INHIBIT)  //value is boolean type    
         $hm->AddParam($HTTP_GET_VARS["value"],2);
     if ($HTTP_GET_VARS["what"] == ON_TIME)      //value is floatingpoint (seconds)   
         $hm->AddParam($HTTP_GET_VARS["value"],4);
   break;
  default:
   break;
  }

$hm->DumpRequest();

if ($hm->ExecuteRequest())
	  $hm->AnalyzeRequest();
?>
please take in mind that this is a working example to show the functionality - so I kept it as simple as possible !!!

How to use:
- copy both files on a web-server with php-support into the same directory
- edit hmcontrol.php to change the ip-address to your HomeMatic

call the wrapper hmcontrol.php from a browser.

examples:

Code: Alles auswählen

http://10.9.1.2/cgi-bin/hmcontrol.php?method=setValue&address=EEQ0255873:1&what=STATE&value=1
* there are 2 methods supported: getValue/setValue
* address is the serial of your devices (take it from the WEB-UI)+ ":channel"
* what is a keyword access the functionality
* value is a parameter used with some functionality

possible functions: method address what value

setValue AddressOfActor:1 STATE 1 -> switch on the actor channel 1
setValue AddressOfActor:2 STATE 0 -> switch off the actor channel 2
setValue AddressOfActor:2 INHIBIT 1 -> inhibit (lock) actor channel 2
setValue AddressOfActor INSTALL_TEST 1 -> toggle state of the actor
getValue AddressOfActor:2 STATE -> query the state of actor channel 2

getValue AddressOfWeatherSensor:1 HUMIDIYT -> get humidity-value
getValue AddressOfWeatherSensor:1 TEMPERATURE -> get temperature-value

set getValue AddressOfActor:2 ON_TIME 2.3
+
setValue AddressOfActor:2 STATE 1 -> switch on actor channel 2 for 2.3 seconds

you can also emulate the pressing of remote-controls via the CCU by using their adresses.

Hope this is clear enough to start your own experiments ;-)

Bye
Alex

Benutzeravatar
over.unity
Beiträge: 348
Registriert: 04.01.2007, 10:20
Wohnort: Frankreich - Elsass

Re: Access CCU (Actors & Sensors) via php

Beitrag von over.unity » 08.03.2008, 11:26

Hey Alex

That’s great, I’ll try this next week!
I analysed the network from the contronics software to the CCU and saw the same on port 2000. Unfortunately it is not possible to catch the switching of the actors by events, but it’s a very good start. Thank you for your work!

Have you any idea, how can I catch the switching events?

andi
-
over.unity

Gross denken, klein beginnen

dantan
Beiträge: 2
Registriert: 13.03.2008, 15:01

Re: Access CCU (Actors & Sensors) via php

Beitrag von dantan » 13.03.2008, 15:07

Nice work you did!

We implemented parts of the code and put it in a php-foreach to fill an array to pass it to a smarty-template.
Right now we have two devices with 4 channels each. All are given as an array.
With one device and one channel the loading-time for the page is about 0.35 sec, with both devices and 2*4 channels it is about 3.7 sec.

All devices/channels are shown on one single site.
Do you have any experience if access-time is so slow or are we doing totally wrong?

Benutzeravatar
a.krypthul
Beiträge: 41
Registriert: 27.01.2008, 13:51
Danksagung erhalten: 1 Mal

Re: Access CCU (Actors & Sensors) via php

Beitrag von a.krypthul » 13.03.2008, 17:38

@dantan

this poor little ARM7 is far away from being a CRAY :wink:

The solution for your problem might be the method system.multicall, which acts as a kind of envelope for multiple rpc-calls. I haven´t yet used it (analyzed the byte structure) but you pack multiple rpc-calls into one single call. E.g eq3 uses a multicall to send weatherinfos (Temperatur + Humidity) from the RF-engine to the system-port.

By chance there is an example for a multicall - have a look at the "portsniffer-screen" at
http://homematic.monkeybits.de/index.php?aid=68#

Hope this helps
Alex

chrispi
Beiträge: 131
Registriert: 25.12.2007, 21:34

Re: Access CCU (Actors & Sensors) via php

Beitrag von chrispi » 23.03.2008, 01:50

Sorry, double posting.
Zuletzt geändert von chrispi am 23.03.2008, 01:52, insgesamt 1-mal geändert.

chrispi
Beiträge: 131
Registriert: 25.12.2007, 21:34

Re: Access CCU (Actors & Sensors) via php

Beitrag von chrispi » 23.03.2008, 01:52

I just tested the performance of the CCU by using 10 to 100 RPCs to query the current temperature of a sensor. For my configuration it took an average time of 5.7 milliseconds per query (including generating the request, send it to the CCU, wait for the answer, receive and parse the answer).

So maybe the script engine and the parsing on the CCU is the bottleneck?

- Christoph

Benutzeravatar
over.unity
Beiträge: 348
Registriert: 04.01.2007, 10:20
Wohnort: Frankreich - Elsass

Re: Access CCU (Actors & Sensors) via php

Beitrag von over.unity » 30.03.2008, 15:45

Hi Alex

I've installed this script on my CCU. hmmm it doesn't work, what is my fault? I just want to know a status:

Code: Alles auswählen

?method=getValue&address=EEQ0004550:14&what=STATE
answer:

Code: Alles auswählen

Request: "getValue"
Param[0]="EEQ0004550:14" Type(3)
Param[1]="STATE" Type(3)

Answer:
Error
I modified the script for debuging, the answer from xml-rpc was:

Code: Alles auswählen

Antwort: ���LBinÿ���D�������� faultCode���ÿÿÿþ���faultString������Unknown instance
-
over.unity

Gross denken, klein beginnen

Benutzeravatar
a.krypthul
Beiträge: 41
Registriert: 27.01.2008, 13:51
Danksagung erhalten: 1 Mal

Re: Access CCU (Actors & Sensors) via php

Beitrag von a.krypthul » 31.03.2008, 07:53

Hi Andi,
over.unity hat geschrieben: hmmm it doesn't work, what is my fault? I just want to know a status:

Code: Alles auswählen

?method=getValue&address=EEQ0004550:14&what=STATE
answer:

Code: Alles auswählen

Request: "getValue"
Param[0]="EEQ0004550:14" Type(3)
Param[1]="STATE" Type(3)
I modified the script for debuging, the answer from xml-rpc was:

Code: Alles auswählen

Antwort: ���LBinÿ���D�������� faultCode���ÿÿÿþ���faultString������Unknown instance

As it says - EEQ0004550:14 is an unknow instance - looking at the channel-no: is it a wired device ? - then the request has to be send to port 2000 (xmlrpc_bin://127.0.0.1:2000 BidCos-Wired) - maybe extend the script by a parameter port ??

Alex

Benutzeravatar
over.unity
Beiträge: 348
Registriert: 04.01.2007, 10:20
Wohnort: Frankreich - Elsass

Re: Access CCU (Actors & Sensors) via php

Beitrag von over.unity » 31.03.2008, 10:50

a.krypthul hat geschrieben:is it a wired device ? - then the request has to be send to port 2000
*sh..* yes, of course....! thank's!
-
over.unity

Gross denken, klein beginnen

dantan
Beiträge: 2
Registriert: 13.03.2008, 15:01

Re: Access CCU (Actors & Sensors) via php

Beitrag von dantan » 02.04.2008, 17:15

I just tested the performance of the CCU by using 10 to 100 RPCs to query the current temperature of a sensor. For my configuration it took an average time of 5.7 milliseconds per query (including generating the request, send it to the CCU, wait for the answer, receive and parse the answer).

So maybe the script engine and the parsing on the CCU is the bottleneck?

- Christoph
Maybe you can give some advice.

We have several devices with up to 4 switches.

My php-script has a function to check the state of a switch
kinda like: $hm->checkState('EEQ0004550:14');

The return value is 1 or 0 or a Temperatur etc...
Every run of the function takes about 0.4 seconds.

Everything less a second would be nice.
To get the states of every switch I have an array with all the devices (array('EEQ0004550:14','EEQ0004550:2','EEQ0004533:5'.........).
Then I do a foreach with the function, of course taking a long time.

What exactly do you mean with RPC?
Is there a way to pass all the array values in once and get one result array back?
Maybe with Param[0]="EEQ0004550:14" etc?

Hope you know what I mean.

Antworten

Zurück zu „Softwareentwicklung von externen Applikationen“