Last active
October 19, 2024 15:35
-
-
Save 667bdrm/209bf33b2d04b08bb318 to your computer and use it in GitHub Desktop.
Simple clock synchronization for some chinese DVRs supporting CMS with json-like protocol. Includes some other API commands. Fork at https://gitlab.com/667bdrm/sofiactl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
# latest release at https://gitlab.com/667bdrm/sofiactl | |
# | |
# Simple clock synchronization for some chinese HiSilicon based DVRs supporting CMS (Sofia software) with json-like protocol. Tested with: | |
# | |
# HJCCTV HJ-H4808BW (XiongMai, Hi3520, MBD6304T) | |
# http://www.aliexpress.com/item/Hybird-NVR-8chs-H-264DVR-8chs-onvif-2-3-Economical-DVR-8ch-Video-4-AUDIO-AND/1918734952.html | |
# | |
# | |
# PBFZ TCV-UTH200 (XiongMai, Hi3518, 50H20L_S39) | |
# http://www.aliexpress.com/item/Free-shipping-2014-NEW-IP-camera-CCTV-2-0MP-HD-1080P-IP-Network-Security-CCTV-Waterproof/1958962188.html | |
# | |
# Additional protocol reference : https://github.com/charmyin/IPCTimeLapse | |
# vendor sdk: https://github.com/mondwan/cpp-surveillance-cli | |
# vendor: http://www.xiongmaitech.com | |
# vendor specifications: http://wiki.xm030.com:81/ | |
package IPcam; | |
use Module::Load::Conditional qw[can_load check_install requires]; | |
my $use_list = { | |
'IO::Socket' => undef, | |
'IO::Socket::INET' => undef, | |
'Time::Local' => undef, | |
JSON => undef, | |
'Data::Dumper' => undef, | |
}; | |
if (!can_load( modules => $use_list, autoload => true )) { | |
die('Failed to load required modules: ' . join(', ', keys %{$use_list})); | |
} | |
use constant { | |
LOGIN_REQ1 => 999, | |
LOGIN_REQ2 => 1000, | |
LOGIN_RSP => 1000, | |
LOGOUT_REQ => 1001, | |
LOGOUT_RSP => 1002, | |
FORCELOGOUT_REQ => 1003, | |
FORCELOGOUT_RSP => 1004, | |
KEEPALIVE_REQ => 1006, # 1005 | |
KEEPALIVE_RSP => 1007, # 1006 | |
SYSINFO_REQ => 1020, | |
SYSINFO_RSP => 1021, | |
CONFIG_SET => 1040, | |
CONFIG_SET_RSP => 1041, | |
CONFIG_GET => 1042, | |
CONFIG_GET_RSP => 1043, | |
DEFAULT_CONFIG_GET => 1044, | |
DEFAULT_CONFIG_GET_RSP => 1045, | |
CONFIG_CHANNELTILE_SET => 1046, | |
CONFIG_CHANNELTILE_SET_RSP => 1047, | |
CONFIG_CHANNELTILE_GET => 1048, | |
CONFIG_CHANNELTILE_GET_RSP => 1049, | |
CONFIG_CHANNELTILE_DOT_SET => 1050, | |
CONFIG_CHANNELTILE_DOT_SET_RSP => 1051, | |
SYSTEM_DEBUG_REQ => 1052, | |
SYSTEM_DEBUG_RSP => 1053, | |
ABILITY_GET => 1360, | |
ABILITY_GET_RSP => 1361, | |
PTZ_REQ => 1400, | |
PTZ_RSP => 1401, | |
MONITOR_REQ => 1410, | |
MONITOR_RSP => 1411, | |
MONITOR_DATA => 1412, | |
MONITOR_CLAIM => 1413, | |
MONITOR_CLAIM_RSP => 1414, | |
PLAY_REQ => 1420, | |
PLAY_RSP => 1421, | |
PLAY_DATA => 1422, | |
PLAY_EOF => 1423, | |
PLAY_CLAIM => 1424, | |
PLAY_CLAIM_RSP => 1425, | |
DOWNLOAD_DATA => 1426, | |
TALK_REQ => 1430, | |
TALK_RSP => 1431, | |
TALK_CU_PU_DATA => 1432, | |
TALK_PU_CU_DATA => 1433, | |
TALK_CLAIM => 1434, | |
TALK_CLAIM_RSP => 1435, | |
FILESEARCH_REQ => 1440, | |
FILESEARCH_RSP => 1441, | |
LOGSEARCH_REQ => 1442, | |
LOGSEARCH_RSP => 1443, | |
FILESEARCH_BYTIME_REQ => 1444, | |
FILESEARCH_BYTIME_RSP => 1445, | |
SYSMANAGER_REQ => 1450, | |
SYSMANAGER_RSP => 1451, | |
TIMEQUERY_REQ => 1452, | |
TIMEQUERY_RSP => 1453, | |
DISKMANAGER_REQ => 1460, | |
DISKMANAGER_RSP => 1461, | |
FULLAUTHORITYLIST_GET => 1470, | |
FULLAUTHORITYLIST_GET_RSP => 1471, | |
USERS_GET => 1472, | |
USERS_GET_RSP => 1473, | |
GROUPS_GET => 1474, | |
GROUPS_GET_RSP => 1475, | |
ADDGROUP_REQ => 1476, | |
ADDGROUP_RSP => 1477, | |
MODIFYGROUP_REQ => 1478, | |
MODIFYGROUP_RSP => 1479, | |
DELETEGROUP_REQ => 1480, | |
DELETEGROUP_RSP => 1481, | |
ADDUSER_REQ => 1482, | |
ADDUSER_RSP => 1483, | |
MODIFYUSER_REQ => 1484, | |
MODIFYUSER_RSP => 1485, | |
DELETEUSER_REQ => 1486, | |
DELETEUSER_RSP => 1487, | |
MODIFYPASSWORD_REQ => 1488, | |
MODIFYPASSWORD_RSP => 1489, | |
GUARD_REQ => 1500, | |
GUARD_RSP => 1501, | |
UNGUARD_REQ => 1502, | |
UNGUARD_RSP => 1503, | |
ALARM_REQ => 1504, | |
ALARM_RSP => 1505, | |
NET_ALARM_REQ => 1506, | |
NET_ALARM_REQ => 1507, | |
ALARMCENTER_MSG_REQ => 1508, | |
UPGRADE_REQ => 1520, | |
UPGRADE_RSP => 1521, | |
UPGRADE_DATA => 1522, | |
UPGRADE_DATA_RSP => 1523, | |
UPGRADE_PROGRESS => 1524, | |
UPGRADE_INFO_REQ => 1525, | |
UPGRADE_INFO_RSQ => 1526, | |
IPSEARCH_REQ => 1530, | |
IPSEARCH_RSP => 1531, | |
IP_SET_REQ => 1532, | |
IP_SET_RSP => 1533, | |
CONFIG_IMPORT_REQ => 1540, | |
CONFIG_IMPORT_RSP => 1541, | |
CONFIG_EXPORT_REQ => 1542, | |
CONFIG_EXPORT_RSP => 1543, | |
LOG_EXPORT_REQ => 1544, #CONDIG_EXPORT_REQ | |
LOG_EXPORT_RSP => 1545, #CONFIG_EXPORT_RSP | |
NET_KEYBOARD_REQ => 1550, | |
NET_KEYBOARD_RSP => 1551, | |
NET_SNAP_REQ => 1560, | |
NET_SNAP_RSP => 1561, | |
SET_IFRAME_REQ => 1562, | |
SET_IFRAME_RSP => 1563, | |
RS232_READ_REQ => 1570, | |
RS232_READ_RSP => 1571, | |
RS232_WRITE_REQ => 1572, | |
RS232_WRITE_RSP => 1573, | |
RS485_READ_REQ => 1574, | |
RS485_READ_RSP => 1575, | |
RS485_WRITE_REQ => 1576, | |
RS485_WRITE_RSP => 1577, | |
TRANSPARENT_COMM_REQ => 1578, | |
TRANSPARENT_COMM_RSP => 1579, | |
RS485_TRANSPARENT_DATA_REQ => 1580, | |
RS485_TRANSPARENT_DATA_RSP => 1581, | |
RS232_TRANSPARENT_DATA_REQ => 1582, | |
RS232_TRANSPARENT_DATA_RSP => 1583, | |
SYNC_TIME_REQ => 1590, | |
SYNC_TIME_RSP => 1591, | |
PHOTO_GET_REQ => 1600, | |
PHOTO_GET_RSP => 1601, | |
}; | |
sub new { | |
my $classname = shift; | |
my $self = {}; | |
bless($self, $classname); | |
$self->_init(@_); | |
return $self; | |
} | |
sub DESTROY { | |
my $self = shift; | |
} | |
sub disconnect { | |
my $self = shift; | |
$self->{socket}->close(); | |
} | |
sub _init { | |
my $self = shift; | |
$self->{host} = ""; | |
$self->{port} = 0; | |
$self->{user} = ""; | |
$self->{password} = ""; | |
$self->{socket} = undef; | |
$self->{sid} = 0; | |
$self->{SystemInfo} = undef; | |
$self->{GenericInfo} = undef; | |
$self->{lastcommand} = undef; | |
$self->{sequence} = 0; | |
if (@_) { | |
my %extra = @_; | |
@$self{keys %extra} = values %extra; | |
} | |
} | |
sub getDeviceRuntime { | |
my $self = shift; | |
$self->getSystemInfo(); | |
my $total_minutes = hex($self->{SystemInfo}->{DeviceRunTime}); | |
my $total_hours = $total_minutes / 60; | |
my $total_days = $total_minutes / (60*24); | |
my $left_minutes = $total_minutes % (60*24); | |
my $hours = int($left_minutes / 60); | |
my $minutes = int($left_minutes % 60); | |
my $years = $total_days / 365; | |
my $left_days = $total_days % 365; | |
my $months = int( $left_days / 30); | |
my $days = $left_days % 30; | |
$total_minutes -= $months * 24 * 60; | |
$total_hours = int($total_hours); | |
$total_days = int($total_days); | |
$runtime = sprintf("%d day(s): %d year(s), %d month(s), %d day(s), %d hour(s), %d minute(s)", $total_days, $years, $months, $days, $hours, $minutes); | |
return $runtime; | |
} | |
sub BuildPacketSid { | |
my $self = shift; | |
return sprintf("0x%08x",$self->{sid}); | |
} | |
sub BuildPacket { | |
my $self = shift; | |
my ($type, $params) = @_; | |
my @pkt_prefix_1; | |
my @pkt_prefix_2; | |
my $pkt_type; | |
my $json = JSON->new; | |
@pkt_prefix_1 = (0xff, 0x01, 0x00, 0x00); # (head_flag, version, reserved01, reserved02) | |
@pkt_prefix_2 = (0x00, 0x00, 0x00, 0x00); # (total_packets, cur_packet) | |
$pkt_type = $type; | |
#my $msgid = pack('c*', @$pkt_type); | |
my $msgid = pack('s', 0) . pack('s', $pkt_type); | |
my $pkt_prefix_data = pack('c*', @pkt_prefix_1) . pack('i', $self->{sid}) . pack('c*', @pkt_prefix_2). $msgid; | |
my $pkt_params_data = $json->encode($params); | |
my $pkt_data = $pkt_prefix_data . pack('i', length($pkt_params_data)) . $pkt_params_data; | |
$self->{lastcommand} = $params->{Name} . sprintf(" msgid = %d", $pkt_type); | |
return $pkt_data; | |
} | |
sub GetReplyHead { | |
my $self = shift; | |
my $data; | |
my @reply_head_array; | |
# head_flag, version, reserved | |
$self->{socket}->recv($data, 4); | |
my @header = unpack('C*', $data); | |
my ($head_flag, $version, $reserved01, $reserved02) = (@header)[0,1,2,3]; | |
# int sid, int seq | |
$self->{socket}->recv($data, 8); | |
my ($sid,$seq) = unpack('i*', $data); | |
$reply_head_array[3] = (); | |
$self->{socket}->recv($data, 8); | |
my ($channel,$endflag,$msgid,$size) = unpack('CCSI', $data); | |
my $reply_head = { | |
Version => $version, | |
SessionID => $sid, | |
Sequence => $seq, | |
MessageId => $msgid, | |
Content_Length => $size, | |
}; | |
$self->{sequence} = $reply_head->{Sequence}; | |
printf("reply: head_flag=%x version=%d session=0x%x sequence=%d channel=%d end_flag=%d msgid=%d size = %d lastcommand = %s\n", $head_flag, $version, $sid, $seq, $channel, $end_flag, $msgid, $size, $self->{lastcommand}); | |
return $reply_head; | |
} | |
sub GetReplyData { | |
my $self = shift; | |
my $reply = $_[0]; | |
my $length = $reply->{'Content_Length'}; | |
my $out; | |
for (my $downloaded=0; $downloaded < $length; $downloaded++) { | |
$self->{socket}->recv($data, 1); | |
$out .= $data; | |
} | |
return $out; | |
} | |
sub getSystemInfo { | |
my $self = shift; | |
if ($self->{SystemInfo} eq undef) { | |
$self->CmdSystemInfo(); | |
} | |
return $self->{SystemInfo}; | |
} | |
sub PrepareGenericCommandHead { | |
my $self = shift; | |
my $msgid = $_[0]; | |
my $parameters = $_[1]; | |
my $data; | |
my $pkt = $parameters; | |
if ($msgid ne LOGIN_REQ2) { | |
$parameters->{SessionID} = $self->BuildPacketSid(); | |
} | |
my $cmd_data = $self->BuildPacket($msgid, $pkt); | |
$self->{socket}->send($cmd_data); | |
my $reply_head = $self->GetReplyHead(); | |
return $reply_head; | |
} | |
sub PrepareGenericCommand { | |
my $self = shift; | |
my $msgid = $_[0]; | |
my $parameters = $_[1]; | |
my $reply_head = $self->PrepareGenericCommandHead($msgid, $parameters); | |
my $out = $self->GetReplyData($reply_head); | |
if ($msgid eq LOGIN_REQ2 and exists $reply_head->{SessionID}) { | |
$self->{sid} = hex($reply_head->{SessionID}); | |
} | |
if ($out) { | |
return decode_json($out); | |
} | |
return undef; | |
} | |
sub PrepareGenericDownloadCommand { | |
my $self = shift; | |
my $msgid = $_[0]; | |
my $parameters = $_[1]; | |
my $file = $_[2]; | |
my $reply_head = $self->PrepareGenericCommandHead($msgid, $parameters); | |
my $out = $self->GetReplyData($reply_head); | |
open(OUT, ">$file"); | |
print OUT $out; | |
close(OUT); | |
return 1; | |
} | |
sub CmdLogin { | |
my $self = shift; | |
my $data; | |
my $pkt = { | |
EncryptType => "MD5", | |
LoginType => "DVRIP-Web", | |
PassWord => $self->{password}, | |
UserName => $self->{user} | |
}; | |
print Dumper $pkt; | |
$reply_json = $self->PrepareGenericCommand(LOGIN_REQ2, $pkt); | |
$self->{GenericInfo} = $reply_json; | |
return $reply_json; | |
} | |
sub CmdSystemInfo { | |
my $self = shift; | |
my $pkt = { | |
Name => 'SystemInfo', | |
}; | |
my $systeminfo = $self->PrepareGenericCommand(SYSINFO_REQ, $pkt); | |
$self->{SystemInfo} = $systeminfo->{SystemInfo}; | |
return $systeminfo; | |
} | |
sub CmdAlarmInfo { | |
my $self = shift; | |
my $parameters = $_[0]; | |
my $pkt = { | |
Name => 'AlarmInfo', | |
AlarmInfo => $parameters, | |
}; | |
return $self->PrepareGenericCommand(ALARM_REQ, $pkt); | |
} | |
sub CmdOPNetAlarm { | |
my $self = shift; | |
my $pkt = { | |
Name => 'OPNetAlarm', | |
NetAlarmInfo => { | |
Event => 0, | |
State => 1, | |
}, | |
}; | |
return $self->PrepareGenericCommand(NET_ALARM_REQ, $pkt); | |
} | |
sub CmdAlarmCenterMsg { | |
my $self = shift; | |
my $data; | |
my $pkt = { | |
Name => 'NetAlarmCenter', | |
NetAlarmCenterMsg => { | |
Address => "0x0B0A060A", | |
Channel => 0, | |
Descrip => "", | |
Event => "MotionDetect", | |
SerialID => "003344236523", | |
StartTime => "2010-06-24 17:04:22", | |
Status => "Stop", | |
Type => "Alarm", | |
}, | |
}; | |
my $cmd_data = $self->BuildPacket(ALARMCENTER_MSG_REQ, $pkt); | |
$self->{socket}->send($cmd_data); | |
my $reply_head = $self->GetReplyHead(); | |
my $out = $self->GetReplyData($reply_head); | |
return decode_json($out); | |
} | |
sub CmdOPNetKeyboard { | |
my $self = shift; | |
my $parameters = $_[0]; | |
my $pkt = { | |
Name => 'OPNetKeyboard', | |
OPNetKeyboard => $parameters, | |
}; | |
return $self->PrepareGenericCommand(NET_KEYBOARD_REQ, $pkt); | |
} | |
sub CmdUsers { | |
my $self = shift; | |
my $pkt = { | |
}; | |
return $self->PrepareGenericCommand(USERS_GET, $pkt); | |
} | |
sub CmdGroups { | |
my $self = shift; | |
my $pkt = { | |
}; | |
return $self->PrepareGenericCommand(GROUPS_GET, $pkt); | |
} | |
sub CmdStorageInfo { | |
my $self = shift; | |
my $pkt = { | |
Name => 'StorageInfo', | |
}; | |
return $self->PrepareGenericCommand(SYSINFO_REQ, $pkt); | |
} | |
sub CmdWorkState { | |
my $self = shift; | |
my $pkt = { | |
Name => 'WorkState', | |
}; | |
return $self->PrepareGenericCommand(SYSINFO_REQ, $pkt); | |
} | |
sub CmdSnap { | |
my $self = shift; | |
my $pkt = { | |
Name => 'OPSNAP', | |
}; | |
return $self->PrepareGenericCommand(NET_SNAP_REQ, $pkt); | |
} | |
sub CmdEmpty { | |
my $self = shift; | |
my $pkt = { | |
Name => '', | |
}; | |
return $self->PrepareGenericCommand(SYSINFO_REQ, $pkt); | |
} | |
sub CmdKeepAlive { | |
my $self = shift; | |
my $pkt = { | |
Name => 'KeepAlive', | |
}; | |
return $self->PrepareGenericCommand(KEEPALIVE_REQ, $pkt); | |
} | |
sub CmdOPMonitorClaim { | |
my $self = shift; | |
my $pkt = { | |
Name => 'OPMonitor', | |
SessionID => $self->BuildPacketSid(), | |
OPMonitor => { | |
Action => "Claim", | |
Parameter => { | |
Channel => 0, | |
CombinMode => "NONE", | |
StreamType => "Extra1", | |
TransMode => "TCP" | |
} | |
} | |
}; | |
my $cmd_data = $self->BuildPacket(MONITOR_CLAIM, $pkt); | |
$self->{socket}->send($cmd_data); | |
my $reply = $self->GetReplyHead(); | |
for my $k (keys %{$reply}) { | |
print "rh = $k\n"; | |
} | |
my $out = $self->GetReplyData($reply); | |
my $out1 = decode_json($out); | |
# $self->{socket}->recv($data, 1); | |
return $out1; | |
} | |
sub CmdOPMonitorStop { | |
my $self = shift; | |
my $pkt = { | |
Name => 'OPMonitor', | |
SessionID => $self->BuildPacketSid(), | |
OPMonitor => { | |
Action => "Stop", | |
Parameter => { | |
Channel => 0, | |
CombinMode => "NONE", | |
StreamType => "Extra1", | |
TransMode => "TCP" | |
} | |
} | |
}; | |
my $cmd_data = $self->BuildPacket(MONITOR_REQ, $pkt); | |
$self->{socket}->send($cmd_data); | |
my $reply = $self->GetReplyHead(); | |
for my $k (keys %{$reply}) { | |
print "rh = $k\n"; | |
} | |
my $out = $self->GetReplyData($reply); | |
my $out1 = decode_json($out); | |
# $self->{socket}->recv($data, 1); | |
return $out1; | |
} | |
sub CmdOPMonitorStart { | |
my $self = shift; | |
my $data; | |
my $pkt = { | |
Name => 'OPMonitor', | |
SessionID => $self->BuildPacketSid(), | |
OPMonitor => { | |
Action => "Start", | |
Parameter => { | |
Channel => 0, | |
CombinMode => "NONE", | |
StreamType => "Extra1", | |
TransMode => "TCP" | |
} | |
} | |
}; | |
my $cmd_data = $self->BuildPacket(MONITOR_REQ, $pkt); | |
$self->{socket}->send($cmd_data); | |
open(OUT, ">> ".$self->{sid}.".h264"); | |
$stop = 0; | |
while (defined( my $reply = $self->GetReplyHead()) and $stop == 0 ) { | |
if (sprintf("%x", $reply->{Data1}) ne "12ff") { | |
for my $k (keys %{$reply}) { | |
print "rh = $k\n"; | |
} | |
print "Content_Length = " . $reply->{Content_Length} . "\n"; | |
my $out = $self->GetReplyData($reply); | |
print OUT $out; | |
if ($reply->{Sequence} > 3) { | |
#$stop = 1; | |
$self->CmdKeepAlive(); | |
} | |
} else { | |
$stop = 1; | |
break; | |
} | |
} | |
close(OUT); | |
return $out1; | |
} | |
sub CmdOPTimeSetting { | |
my $self = shift; | |
my $nortc = $_[0]; | |
my $data; | |
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(); | |
my $clock_cmd = 'OPTimeSetting'; | |
my $pkt_type = SYSMANAGER_REQ; | |
if ($nortc eq 1) { | |
$clock_cmd .= 'NoRTC'; | |
$pkt_type = SYNC_TIME_REQ; | |
} | |
my $pkt = { | |
Name => $clock_cmd, | |
SessionID => $self->BuildPacketSid(), | |
"$clock_cmd" => sprintf("%4d-%02d-%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec) | |
}; | |
my $cmd_data = $self->BuildPacket($pkt_type, $pkt); | |
$self->{socket}->send($cmd_data); | |
my $reply = $self->GetReplyHead(); | |
my $out = $self->GetReplyData($reply); | |
if ($out) { | |
return decode_json($out); | |
} | |
return undef; | |
} | |
#buggy | |
sub CmdSystemFunction { | |
my $self = shift; | |
my $pkt = { | |
Name => 'SystemFunction', | |
}; | |
return $self->PrepareGenericCommand(ABILITY_REQ, $pkt); | |
} | |
sub CmdOPFileQuery { | |
my $self = shift; | |
my $parameters = $_[0]; | |
my $pkt = { | |
Name => 'OPFileQuery', | |
OPFileQuery => $parameters, | |
}; | |
return $self->PrepareGenericCommand(FILESEARCH_REQ, $pkt); | |
} | |
sub CmdOEMInfo { | |
my $self = shift; | |
my $pkt = { | |
Name => 'OEMInfo', | |
}; | |
return $self->PrepareGenericCommand(SYSINFO_REQ, $pkt); | |
} | |
sub CmdOPPlayBack { | |
my $self = shift; | |
my $parameters = $_[0]; | |
my $pkt = { | |
Name => 'OPPlayBack', | |
OPPlayBack => $parameters, | |
}; | |
return $self->PrepareGenericCommand(PLAY_REQ, $pkt); | |
} | |
sub CmdOPLogQuery { | |
my $self = shift; | |
my $parameters = $_[0]; | |
my $pkt = { | |
Name => 'OPLogQuery', | |
OPLogQuery => $parameters, | |
}; | |
return $self->PrepareGenericCommand(LOGSEARCH_REQ, $pkt); | |
} | |
sub LogExport { | |
my $self = shift; | |
my $file = $_[0]; | |
my $pkt = { | |
Name => '', | |
}; | |
return $self->PrepareGenericDownloadCommand(LOG_EXPORT_REQ, $pkt, $file); | |
} | |
sub ConfigExport { | |
my $self = shift; | |
my $file = $_[0]; | |
my $pkt = { | |
Name => '', | |
}; | |
return $self->PrepareGenericDownloadCommand(CONFIG_EXPORT_REQ, $pkt, $file); | |
} | |
sub CmdOPStorageManager { | |
my $self = shift; | |
my $data; | |
my $parameters = $_[0]; | |
my $pkt = { | |
Name => 'OPStorageManager', | |
'OPStorageManager' => $parameters, | |
SessionID => $self->BuildPacketSid(), | |
}; | |
return $self->PrepareGenericCommand(DISKMANAGER_REQ, $pkt); | |
} | |
package main; | |
use IO::Socket; | |
use IO::Socket::INET; | |
use Time::Local; | |
use Getopt::Long; | |
use Pod::Usage; | |
use Data::Dumper; | |
my $cfgFile = ""; | |
my $cfgUser = ""; | |
my $cfgPass = ""; | |
my $cfgHost = ""; | |
my $cfgPort = ""; | |
my $cfgCmd = undef; | |
my $help = 0; | |
my $result = GetOptions ( | |
"help|h" => \$help, | |
"outputfile|of|o=s" => \$cfgFile, | |
"user|u=s" => \$cfgUser, | |
"pass|p=s" => \$cfgPass, | |
"host|hst=s" => \$cfgHost, | |
"port|prt=s" => \$cfgPort, | |
"command|cmd|c=s" => \$cfgCmd, | |
); | |
pod2usage(1) if ($help); | |
if (!($cfgHost or $cfgPort or $cfgUser)) { | |
print STDERR "You must set user, host and port!\n"; | |
exit(0); | |
} | |
my $socket = IO::Socket::INET->new( | |
PeerAddr => $cfgHost, | |
PeerPort => $cfgPort, | |
Proto => 'tcp', | |
Timeout => 10000, | |
Type => SOCK_STREAM, | |
Blocking => 1 | |
) or die "Error at line " . __LINE__. ": $!\n"; | |
print "Setting clock for: host = $cfgHost port = $cfgPort\n"; | |
my $dvr = IPcam->new(host => $cfgHost, port => $cfgPort, user => $cfgUser, password => $cfgPass, socket => $socket); | |
my $savePath = '/tmp'; | |
my $decoded = $dvr->CmdLogin(); | |
#Ret = 100 - login ok | |
#Ret = 205 - bad login | |
#Ret = 101 - ??? | |
$aliveInterval = $decoded->{'AliveInterval'}; | |
print sprintf("SessionID = 0x%08x\n",$dvr->{sid}); | |
print sprintf("AliveInterval = %d\n",$aliveInterval); | |
my $decoded = $dvr->CmdSystemInfo(); | |
print Dumper $dvr->{GenericInfo}; | |
print Dumper $dvr->getSystemInfo(); | |
print "System running:" . $dvr->getDeviceRuntime() ."\n"; | |
if ($cfgCmd eq "OPTimeSetting") { | |
$decoded = $dvr->CmdOPTimeSetting(1); | |
$decoded = $dvr->CmdOPTimeSetting(); | |
} elsif ($cfgCmd eq "Users") { | |
$decoded = $dvr->CmdUsers(); | |
} elsif ($cfgCmd eq "Groups") { | |
$decoded = $dvr->CmdGroups(); | |
} elsif ($cfgCmd eq "StorageInfo") { | |
$decoded = $dvr->CmdStorageInfo(); | |
} elsif ($cfgCmd eq "WorkState") { | |
$decoded = $dvr->CmdWorkState(); | |
} elsif ($cfgCmd eq "LogExport") { | |
$decoded = $dvr->LogExport($cfgFile); | |
} elsif ($cfgCmd eq "ConfigExport") { | |
$decoded = $dvr->ConfigExport($cfgFile); | |
} elsif ($cfgCmd eq "OEMInfo") { | |
$decoded = $dvr->CmdOEMInfo(); | |
} elsif ($cfgCmd eq "OPStorageManagerClear") { | |
$decoded = $dvr->CmdOPStorageManager({ | |
Action => "Clear", | |
PartNo => 0, | |
SerialNo => 0, | |
Type => "Data", | |
}); | |
} elsif ($cfgCmd eq "OPStorageManagerPartition") { | |
$decoded = $dvr->CmdOPStorageManager({ | |
Action => "Partition", | |
PartNo => 0, | |
PartitionSize => ( { "Record" => 853869 }, { "SnapShot" => 100000 } ), | |
SerialNo => 0 , | |
}); | |
} elsif ($cfgCmd eq "OPStorageManagerRecover") { | |
$decoded = $dvr->CmdOPStorageManager({ | |
Action => "Recover", | |
PartNo => 0, | |
SerialNo => 0, | |
}); | |
} elsif ($cfgCmd eq "OPStorageManagerRW") { | |
$decoded = $dvr->CmdOPStorageManager({ | |
Action => "SetType", | |
PartNo => 0, | |
SerialNo => 0, | |
Type => "ReadWrite", | |
}); | |
} elsif ($cfgCmd eq "OPStorageManagerRO") { | |
$decoded = $dvr->CmdOPStorageManager({ | |
Action => "SetType", | |
PartNo => 0, | |
SerialNo => 0, | |
Type => "ReadOnly", | |
}); | |
} | |
print Dumper $decoded; | |
#my $decoded = $dvr->CmdSystemFunction(); | |
#my $decoded = $dvr->CmdOPFileQuery({ | |
# BeginTime => "2016-07-01 22:00:00", | |
# EndTime => "2016-07-01 23:59:59", | |
# Channel => 1, | |
# # search all channels instead of single | |
# #HighChannel => 0, | |
# #LowChannel => 255, | |
# DriverTypeMask => "0x0000FFFF", | |
# Event => "*", # * - All; A - Alarm; M - Motion Detect; R - General; H - Manual; | |
# Type => "h264" #h264 or jpg | |
#}); | |
#my $decoded = $dvr->CmdOPPlayBack({ | |
# Action => "Claim", | |
# EndTime => '2015-05-04 17:59:24', | |
# StartTime => '2015-05-04 17:59:09', | |
# Parameter => { | |
# FileName => '/idea0/2015-05-04/002/17.59.09-17.59.24[M][@12480][0].h264', | |
# PlayMode => "ByName", | |
# TransMode => "TCP", | |
# Value => 0 | |
# } | |
#}); | |
my $decoded = $dvr->CmdAlarmInfo({ | |
Channel => 0, | |
Event => "VideoMotion", | |
StartTime => "2016-07-03 03:36:11", | |
Status => "Start" | |
}); | |
#my $decoded = $dvr->CmdOPNetAlarm(); #FIXME | |
#my $decoded = $dvr->CmdAlarmCenterMsg(); #FIXME | |
my $decoded = $dvr->CmdOPNetKeyboard({ | |
Status => "KeyUp", | |
Value => "0", | |
}); | |
#my $decoded = $dvr->CmdSnap(); #FIXME unsuppoted? | |
#my $decoded = $dvr->CmdOPLogQuery({ | |
# BeginTime => "2014-01-01 00:00:00", | |
# EndTime => "2016-06-29 00:00:00", | |
# LogPosition => 0, | |
# Type => "LogAll", | |
#}); | |
#my $pkt = { | |
# Name => '', | |
#}; | |
#my $decoded = $dvr->PrepareGenericDownloadCommand(IPcam::PHOTO_GET_REQ, $pkt, "out.dat"); | |
print Dumper $decoded; | |
$dvr->disconnect(); | |
__END__ | |
=head1 NAME | |
./dvr.pl - utility for working with nvr data | |
=head1 SYNOPSIS | |
./dvr.pl [options] | |
=head1 OPTIONS | |
=over 8 | |
=item B<-help> | |
Print a brief help message and exits. | |
=item B<-of> | |
Path to output file filename. | |
=item B<-u> | |
username | |
=item B<-p> | |
password | |
=item B<-host> | |
nvr hostname or ip address | |
=item B<-port> | |
nvr CMS port | |
=item B<-c> | |
nvr command: OPTimeSetting, Users, Groups, WorkState, StorageInfo, OEMInfo, LogExport, ConfigExport, OPStorageManagerClear | |
=back | |
=head1 DESCRIPTION | |
B<This program> can control the NVR. | |
=cut | |
Latest version at https://github.com/667bdrm/sofiactl supporting hash calculation by default, no needs to grab it by sniffer.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I managed to get this script talking to my NVR device (https://www.amazon.co.uk/gp/product/B017DCMB22)
I used the following command line:
perl ipcam-clock-set.pl -u admin -p tlJwpbo6 -host 192.168.1.137 -port 34567
The password in the above command is actually blank (an empty string) that has been hashed by some unknown algorithm. I captured it using Wireshark whilst logging in via the ActiveX based web client. I came across this paper which discusses some aspects of these NVR systems which I found quite interesting: http://www.i-1.nl/blog/wp-content/uploads/CCF-paper-Forensic-reliabilty-DVR.pdf
Here is the output of the command:
I'm impressed it managed to connect and talk to the device, I'm not sure what I want to do with it yet but it gives me a starting point.
Thanks!