<?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);
}
}
}
?>