<?PHP
$debug = false;
$camname = "empty";
/ source: https://github.com/riogrande75/Dahua/blob/master/DahuaEventHandler.php
declare(ticks = 1);
function logging($text){
    list($ts) = explode(".",microtime(true));
    $dt = new DateTime(date("Y-m-d H:i:s.",$ts));
    $logdate = $dt->format("Y-m-d H:i:s.u");
    echo $logdate.": ";
    print_r($text);
    echo "\n";
}
function sig_handler($sig) {
    global $Dahua;
    switch($sig) {
        case SIGINT: sleep(1);
        case SIGTERM:
        unset($Dahua); 
        sleep(1);
        logging("beendet!");
        sleep(1);
        break;
        # one branch for signal...
    }
    exit(1);
}
pcntl_signal(SIGINT,  "sig_handler");
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP,  "sig_handler");
switch($argv[1]){
    case 'ipc1': $camname="ipc1_haustuer";
    echo "<** Dahua IPC $camname START **>\n";
    $Dahua = new Dahua_Functions("192.168.178.82", "php", "pass",$camname);
    break;
    case 'ipc2': $camname="ipc2_terrasse";
    echo "<** Dahua IPC $camname START **>\n";
    $Dahua = new Dahua_Functions("192.168.178.81", "php", "pass",$camname);
    break;    
    
    case 'ipc3': $camname="ipc3_garagenhof";
    echo "<** Dahua IPC $camname START **>\n";
    $Dahua = new Dahua_Functions("192.168.178.118", "php", "pass",$camname);
    break;    
    
    case 'suntime':
        $Dahua = new Dahua_Functions(0, 0, 0,0);
        echo $Dahua->SunTime(3); /$argv[2]
        
    exit;
    break;
    
    default:
    echo "no cam select;";
    exit;
    break;
    
}
/set sunset/sunrise time every start:
$Dahua->SunTime(1);
$Dahua->ToggleLight("off");
$status = $Dahua->Main();
logging("All done");
class Dahua_Functions
{
    private $sock, $host, $port, $credentials, $camname;
    private $ID = 0;                        # Our Request / Responce ID that must be in all requests and initated by us
    private $SessionID = 0;                 # Session ID will be returned after successful login
    private $SID = 0;                       # SID will be returned after we called <service>.attach with 'Object ID'
    private $FakeIPaddr = '(null)';         # WebGUI: mask our real IP
    private $clientType = '';               # WebGUI: We do not show up in logs or online users
    private $keepAliveInterval = 60;
    private $lastKeepAlive = 0;
    private $lightStatus=0;
    private $username, $password;
    private $motionTrigger='no';
    function __destruct() {
        @fclose($this->sock);
        $this->ToggleLight("off");
        return(1);
    }
    function Dahua_Functions($host, $user, $pass, $camname)
    {
        $this->host = $host;
        $this->username = $user;
        $this->password = $pass;
        $this->camname = $camname;
    }
    function Gen_md5_hash($Dahua_random, $Dahua_realm, $username, $password)
    {
        $PWDDB_HASH = strtoupper(md5($username.':'.$Dahua_realm.':'.$password));
        $PASS = $username.':'.$Dahua_random.':'.$PWDDB_HASH;
        $RANDOM_HASH = strtoupper(md5($PASS));
        return $RANDOM_HASH;
    }
    function KeepAlive($delay)
    {
        global $debug;
        logging("Started keepAlive thread");
        while(true){
            $query_args = array(
                'method'=>"global.keepAlive",
                'magic'=>"0x1234",
                'params'=>array(
                    'timeout'=>$delay,
                    'active'=>true
                    ),
                'id'=>$this->ID,
                'session'=>$this->SessionID);
            $this->Send(json_encode($query_args));
            $lastKeepAlive = time();
            $keepAliveReceived = false;
            while($lastKeepAlive + $delay > time()){
                $data = $this->Receive();
                if (!empty($data)){
                    foreach($data as $packet) {
                        $packet = json_decode($packet, true);
                        if(is_array($packet) AND array_key_exists('result', $packet)){
                            if($debug) logging("keepAlive back");
                            $keepAliveReceived = true;
                        }
                        elseif ($packet['method'] == 'client.notifyEventStream'){
                            $status = $this->EventHandler($packet);
                        }
                    }
                }
            }
            if (!$keepAliveReceived){
                logging("keepAlive failed");
                return false;
            }
        }
    }
    function Send($packet)
    {
        if (empty($packet)){
            $packet = '';
        }
        $header = pack("N",0x20000000);
        $header .= pack("N",0x44484950);
        $header .= pack("V",$this->SessionID);
        $header .= pack("V",$this->ID);
        $header .= pack("V",strlen($packet));
        $header .= pack("V",0);
        $header .= pack("V",strlen($packet));
        $header .= pack("V",0);
        if (strlen($header) != 32){
            logging("Binary header != 32 ({})");
            return;
        }
        $this->ID += 1;
        try{
            $msg = $header.$packet;
            $result = fwrite($this->sock, $msg);
        } catch (Exception $e) {
            logging($e);
        }
    }
    function Receive($timeout = 5)
    {
        #
        # We must expect there is no output from remote device
        # Some debug cmd do not return any output, some will return after timeout/failure, most will return directly
        #
        $data = "";
        $P2P_header = "";
        $P2P_data = "";
        $P2P_return_data = [];
        $header_LEN = 0;
        try{
            $len = strlen($data);
            $read = array($this->sock);
            $write = null;
            $except = null;
            $ready = @stream_select($read, $write, $except, $timeout);
            if ($ready > 0) {
                $data .= stream_socket_recvfrom($this->sock, 8192);
            }
        } catch (Exception $e) {
            return "";
        }
        if (strlen($data)==0){
            #logging("Nothing received anything from remote");
            return "";
        }
        $LEN_RECVED = 1;
        $LEN_EXPECT = 1;
        while (strlen($data)>0){
            if (substr($data,0,8) == pack("N",0x20000000).pack("N",0x44484950)){ # DHIP
                $P2P_header = substr($data,0,32);
                $LEN_RECVED = unpack("V",substr($data,16,4))[1];
                $LEN_EXPECT = unpack("V",substr($data,24,4))[1];
                $data = substr($data,32);
            }
            else{
                if($LEN_RECVED > 1){
                    $P2P_data = substr($data,0,$LEN_RECVED);
                    $P2P_return_data[] = $P2P_data;
                }
                $data = substr($data,$LEN_RECVED);
                if ($LEN_RECVED == $LEN_EXPECT && strlen($data)==0){
                    break;
                }
            }
        }
        return $P2P_return_data;
    }
    function Login()
    {
        logging("Start login");
        $query_args = array(
            'id'=>10000,
            'magic'=>"0x1234",
            'method'=>"global.login",
            'params'=>array(
                'clientType'=>$this->clientType,
                'ipAddr'=>$this->FakeIPaddr,
                'loginType'=>"Direct",
                'password'=>"",
                'userName'=>$this->username,
                ),
            'session'=>0
            );
        $this->Send(json_encode($query_args));
        $data = $this->Receive();
        if (empty($data)){
            logging("global.login [random]");
            return false;
        }
        $data = json_decode($data[0], true);
        $this->SessionID = $data['session'];
        $RANDOM = $data['params']['random'];
        $REALM = $data['params']['realm'];
        $RANDOM_HASH = $this->Gen_md5_hash($RANDOM, $REALM, $this->username, $this->password);
        $query_args = array(
            'id'=>10000,
            'magic'=>"0x1234",
            'method'=>"global.login",
            'session'=>$this->SessionID,
            'params'=>array(
                'userName'=>$this->username,
                'password'=>$RANDOM_HASH,
                'clientType'=>$this->clientType,
                'ipAddr'=>$this->FakeIPaddr,
                'loginType'=>"Direct",
                'authorityType'=>"Default",
                )
            );
        $this->Send(json_encode($query_args));
        $data = $this->Receive();
        if (empty($data)){
            return false;
        }
        $data = json_decode($data[0], true);
        if (array_key_exists('result', $data) && $data['result']){
            logging("Login success");
            $this->keepAliveInterval = $data['params']['keepAliveInterval'];
            return true;
        }
        logging("Login failed: ".$data['error']['code']." ".$data['error']['message']);
        return false;
    }
    function Main($reconnectTimeout=60)
    {
        $error = false;
        while (true){
            if($error){
                sleep($reconnectTimeout);
            }
            $error = true;
            $this->sock = @fsockopen($this->host, 80, $errno, $errstr, 5);
            if($errno){
                logging("Socket open failed");
                continue;
            }
            if (!$this->Login()){
                continue;
            }
            #Listen to all events
            $query_args = array(
                'id'=>$this->ID,
                'magic'=>"0x1234",
                'method'=>"eventManager.attach",
                'params'=>array(
                    'codes'=>["All"]
                    ),
                'session'=>$this->SessionID
                );
            $this->Send(json_encode($query_args));
            $data = $this->Receive();
            if (!count($data) || !array_key_exists('result', json_decode($data[0], true))){
                logging("Failure eventManager.attach");
                continue;
            }
            else{
                unset($data[0]);
                foreach($data as $packet) {
                    $packet = json_decode($packet, true);
                    if ($packet['method'] == 'client.notifyEventStream'){
                        $status = $this->EventHandler($packet);
                    }
                }
            }
            $this->KeepAlive($this->keepAliveInterval);
            logging("Failure no keep alive received");
        }
    }
    
    function EventHandler($data)
    {
    global $debug;
    $eventList = $data['params']['eventList'][0];
    $eventCode = $eventList['Code'];
    $eventData = $eventList['Data'];
    if(count($data['params']['eventList'])>1){
        logging("Event Manager subscription reply");
    }
    elseif($eventCode == 'VideoMotion'){
        logging("Event VideoMotion -".$eventList['Action']);
        if($eventList['Action'] == 'Start'){
        if($this->SunTime(3)=="night" AND $this->camname=="ipc2_terrasse"){ $this->ToggleLight("on",15,5); } / licht an! 
            $this->motionTrigger='yes';
        }
        if($eventList['Action'] == 'Stop'){
            $this->ToggleLight("off");    / licht aus!
            $this->motionTrigger='no';
        }
        
    }
    
    elseif($eventCode == 'SmartMotionHuman'){
        logging("Event SmartMotionHuman -".$eventList['Action']);
        if($eventList['Action'] == 'Start'){        
            /$this->SaveSnapshot();
        }
        if($eventList['Action'] == 'Stop'){
            
        }        
        
    }
    
    elseif($eventCode == 'CrossRegionDetection'){
        
        if($eventList['Action'] == 'Start'){
            
            if($eventData['Action']=="Appear" OR $eventData['Action']=="Cross" OR $eventData['Action']=="Inside" OR $eventData['Action']=="Disappear"){
                
            if($this->SunTime(3)=="night" AND ($this->camname=="ipc1_haustuer" OR $this->camname=="ipc3_garagenhof")){ $this->ToggleLight("on",15,4); } / licht an! 
            
                if(is_array($eventData['Object'])){            
                    logging("CrossRegionDetection [".$eventData['Action']."]: ".$eventData['Name'].": ".$eventData['Object']['ObjectType']);
                }
            
            }
            
            
        }
        if($eventList['Action'] == 'Stop'){
            if($this->motionTrigger=='no'){
                /wenn "crossregion appear" kein VideoMotion ausgelöst hat, bleibt licht dauerhaft an, deshalb hier beenden wenn motionTrigger=='no'                
                logging("CrossRegionDetection Stop!");
                $this->ToggleLight("off");
                
            }
            /light aus, bei ende von motiondetect. da sonst zu kurzes event.
        }
        
    }
    
    elseif($eventCode=='LeFunctionStatusSync'){
        if($eventList['Action'] == 'Pulse'){
            logging("StatusSync: ".$eventData['Function'].": ".intval($eventData['Status']));
        }
        
    }
    
    elseif($eventCode=='IntelliFrame'){
        
        
    }
    elseif($eventCode=='InterVideoAccess'){
        
        
    }
    
    
    
    elseif($eventCode == 'RtspSessionDisconnect'){
        if($eventList['Action'] == 'Start'){
            logging("Event Rtsp-Session from ".str_replace("::ffff:","",$eventData['Device'])." disconnected");
        }
        elseif($eventList['Action'] == 'Stop'){
            logging("Event Rtsp-Session from ".str_replace("::ffff:","",$eventData['Device'])." connected");
        }
    }
    elseif($eventCode == 'BackKeyLight'){
        logging("Event BackKeyLight with State ".$eventData['State']." ");
    }
    elseif($eventCode == 'TimeChange'){
        logging("Event TimeChange, BeforeModifyTime: ".$eventData['BeforeModifyTime'].", ModifiedTime: ".$eventData['ModifiedTime']."");
    }
    elseif($eventCode == 'NTPAdjustTime'){
                if($eventData['result']) logging("Event NTPAdjustTime with ".$eventData['Address']." success");
                        else  logging("Event NTPAdjustTime failed");
    }
    elseif($eventCode == 'KeepLightOn'){
        if($eventData['Status'] == 'On'){
            logging("Event KeepLightOn");
        }
        elseif($eventData['Status'] == 'Off'){
            logging("Event KeepLightOff");
        }
    }
    elseif($eventCode == 'VideoBlind'){
        if($eventList['Action'] == 'Start'){
            logging("Event VideoBlind started");
        }
        elseif($eventList['Action'] == 'Stop'){
            logging("Event VideoBlind stopped");
        }
    }
    elseif($eventCode == 'Reboot'){
        logging("Event: Reboot, Action ".$eventList['Action'].", LocaleTime ".$eventData['LocaleTime']);
        }
    elseif($eventCode == 'SecurityImExport'){
        logging("Event: SecurityImExport, Action ".$eventList['Action'].", LocaleTime ".$eventData['LocaleTime'].", Status ".$eventData['Status']);
        }
    elseif($eventCode == 'DGSErrorReport'){
        logging("Event: DGSErrorReport, Action ".$eventList['Action'].", LocaleTime ".$eventData['LocaleTime']);
    }
    elseif($eventCode == 'Upgrade'){
        logging("Event: Upgrade, Action ".$eventList['Action'].", with State".$eventData['State'].", LocaleTime ".$eventData['LocaleTime']);
    }
    elseif($eventCode == 'NetworkChange'){
            logging("Event: NetworkChange, Action ".$eventList['Action'].", LocaleTime ".$eventData['LocaleTime']);
    }
    else{
        logging("Unknown event received");
        file_put_contents('unknownEvent.txt', var_export($data)."\r\n--------------------------------------------------\r\n", FILE_APPEND);
        if($debug) var_dump($data);
    }
    return true;
    }
    
    
    function SaveSnapshot($path=".")
    {
    $filename = $path."/MotionShot_".$this->camname."_".date("Y-m-d_H-i-s").".jpg";
    $fp = fopen($filename, 'wb');
    $url = "http://".$this->host."/cgi-bin/snapshot.cgi";
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
    curl_setopt($ch, CURLOPT_USERPWD, $this->username . ":" . $this->password);
    curl_setopt($ch, CURLOPT_FILE, $fp);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_HTTPGET, 1);
    curl_exec($ch);
    curl_close($ch);
    fclose($fp);
    copy($filename, $path."/aktuelles_event_".$this->camname.".jpg");
    }
    
    
    
    
    function SunTime($setit=0){
        
        
                
        $time = time();
        $latitude = 49.3; $longitude = 10.59;
        / $sunrise = date_sunrise($time, SUNFUNCS_RET_TIMESTAMP, $latitude, $longitude); $sunset  = date_sunset($time, SUNFUNCS_RET_TIMESTAMP, $latitude, $longitude);
         $suninfo = date_sun_info($time, $latitude, $longitude);
         $isunrise = $suninfo['sunrise']; file_put_contents("sunrise.txt",$isunrise);
         $isunset  = $suninfo['sunset']; file_put_contents("sunset.txt",$isunset);
         
         $sunrise = date('H:i:00', $suninfo['sunrise']);
         $sunset = date('H:i:00', $suninfo['sunset']);
                
        
        
        if($setit==0){
        $url = "http://".$this->host."/cgi-bin/configManager.cgi?action=getConfig&name=VideoInMode";
        /getdata;
        }
        
/*
[{"Config":[0,1],"Mode":1,"TimeSection":[["1 08:30:00-19:00:00","0 00:00:00-00:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00"],["0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00"],["0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00"],["0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00"],["0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00"],["0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00"],["0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00","0 00:00:00-24:00:00"]]}]
*/        
        
        
        
        if($setit==1){
            
        $url = "http://".$this->host."/cgi-bin/configManager.cgi?action=setConfig&VideoInMode[0].Mode=1&VideoInMode[0].Config[0]=0&VideoInMode[0].Config[1]=1&VideoInMode[0].TimeSection[0][0]=1%20$sunrise-$sunset&VideoInMode[0].TimeSection[0][1]=0%2000:00:00-24:00:00&VideoInMode[0].TimeSection[0][2]=0%2000:00:00-24:00:00&VideoInMode[0].TimeSection[0][3]=0%2000:00:00-24:00:00&VideoInMode[0].TimeSection[0][4]=0%2000:00:00-24:00:00&VideoInMode[0].TimeSection[0][5]=0%2000:00:00-24:00:00";    
        
        logging("setTimeDayNight: sunrise: $sunrise ; sunset: $sunset ;");
            
        }    
    
        if($setit<3){
        
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
        curl_setopt($ch, CURLOPT_USERPWD, $this->username . ":" . $this->password);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_HTTPGET, 1);
        $ret = curl_exec($ch);
        curl_close($ch);
        var_dump($ret);
        
        }
        
        /logging("sunrise: ".$isunrise." < ".$time);        logging("sunset: ".$isunset." > ".$time);
        
        if($isunrise<$time AND $isunset>$time ){
            return "day";
        }else{
            return "night";
        }
        
        
    }
    
    
    
    function ToggleLight($opt='off',$far=12,$near=2){
        
        global $debug;
        
        $url = $ret = false;
        if($opt=="on" AND $this->lightStatus==0){
            $url = "http://".$this->host."/cgi-bin/configManager.cgi?action=setConfig&Lighting[0][0].FarLight[0].Light=".$far."&Lighting[0][0].NearLight[0].Light=".$near."&Lighting[0][0].Mode=Manual";
            $this->lightStatus = 1;
        }
        if($opt=="off"){ /AND $this->lightStatus==1
            $url = "http://".$this->host."/cgi-bin/configManager.cgi?action=setConfig&Lighting[0][0].FarLight[0].Light=0&Lighting[0][0].NearLight[0].Light=0&Lighting[0][0].Mode=Off";
            $this->lightStatus = 0;
        }
        
        if($url!==false){        
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
        curl_setopt($ch, CURLOPT_USERPWD, $this->username . ":" . $this->password);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_HTTPGET, 1);
        $ret = curl_exec($ch);
        curl_close($ch);
        logging("Light: $opt ; status: ".$this->lightStatus."; ret=".$ret);
        }
        
        if($debug AND ($url==false OR $ret==false)){
            logging("LightToggle: $opt ; status: ".$this->lightStatus);
        }
        
        
        
    }
    
    
}
?>