IPCamera-Reolink

 view release on metacpan or  search on metacpan

lib/IPCamera/Reolink.pm  view on Meta::CPAN

        return $response_content;
    }else{
        my $t5 = Time::HiRes::time() if($DEBUG > 2);
        print STDERR scalar(localtime()) . ": debug: IPCamera::Reolink::_sendCameraCommand($camera_command): " . $t5 . ": call JSON::decode_json()\n" if($DEBUG > 2);

        my $response_r;
        eval{
            $response_r = JSON::decode_json($response_content);
        };
        if($@){
            print STDERR scalar(localtime()) . ": error: IPCamera::Reolink::_sendCameraCommand($camera_command): JSON::decode_json() failed - '$@' - responseContent '$response_content'\n";
            return undef;
        } # if

        my $t6 = Time::HiRes::time() if($DEBUG > 1);
        print STDERR scalar(localtime()) . ": debug: IPCamera::Reolink::_sendCameraCommand($camera_command): JSON encode " . ($t2 - $t1) . " POST " . ($t3 - $t2) . " responseCode " . ($t4 - $t3) . " responseContent " . ($t5 - $t4) . " JSON decode " ....
        if($DEBUG > 1){
            if($DEBUG > 3){
                my @headers = $camera_rest_client->responseHeaders();
                foreach my $header (@headers){
                    print STDERR scalar(localtime()) . ": debug: IPCamera::Reolink::_sendCameraCommand($camera_command): header '" . $header . "' '" . $camera_rest_client->responseHeader($header) . "' \n";
                } # for
            } # if
            my $save_linewidth = $Data::Dump::LINEWIDTH;
            $Data::Dump::LINEWIDTH = 500; # no linebreak
            print STDERR scalar(localtime()) . ": debug: IPCamera::Reolink::_sendCameraCommand($camera_command): command '" . $camera_command . "' token '" . (defined($token) ? $token : 'undef') . "' request '" . Dumper($request_r) . "' response '" ....
            $Data::Dump::LINEWIDTH = $save_linewidth; 
        } # if

        my $code = @$response_r[0]->{code};
        if($response_code ne '200' || $code != 0){
            print STDERR scalar(localtime()) . ": error: IPCamera::Reolink::_sendCameraCommand($camera_command): exit ERROR: response_code '$response_code ' code '$code'\n" if($DEBUG > 2);
            return undef;
        }else{
            print STDERR scalar(localtime()) . ": debug: IPCamera::Reolink::_sendCameraCommand($camera_command): exit OK response_code '$response_code ' code '$code'\n" if($DEBUG > 2);
            return $response_r;
        } # if
    } # if
} # _sendCameraCommand()

# _checkLoginLeaseTime() - determine if camera Login lease for Login token has expired and reacquire on expiry
sub _checkLoginLeaseTime($){
    my($self) = @_;
    my $now = time();
    my($_camera_login_lease_time, $_camera_login_lease_start_time) = ($self->{_camera_login_lease_time}, $self->{_camera_login_lease_start_time});
    if($now >= $_camera_login_lease_start_time + $_camera_login_lease_time){
        # Perhaps re-establish the REST connection as long intervals with no activity seem to make the camera "forget" the connection. @@@
        my($login_token, $login_lease_time) = Login();
        if(defined($login_token)){
            $self->{_camera_login_lease_start_time} = $now; # New Login token lease time
            $self->{_camera_login_lease_time} = $login_lease_time; # New Login token 
            return 1; # new Login token
        }else{
            return 0; # failed to acquire new Login token
        } # if
    }else{
        return 1; # Login token still valid
    } # if
} # _checkLoginLeaseTime()

# Login() - provides login credentials (username/password) to camera and returns API access token good for specified number of seconds
sub Login(){
    my($self) = @_;
    my($_camera_rest_client, $camera_url, $camera_user_name, $camera_password, $camera_X509_certificate_file, $camera_X509_key_file, $camera_certificate_authority_file, $_is_https) = ($self->{_camera_rest_client}, $self->{camera_url}, $self->{camera_...
    if(!defined($_camera_rest_client)){
        # disable check for valid certificate matching the expected hostname
        $_camera_rest_client = REST::Client->new(host => $camera_url, timeout => 10, cert => $camera_X509_certificate_file, key => $camera_X509_key_file, ca => $camera_certificate_authority_file);
        $_camera_rest_client->getUseragent()->ssl_opts(verify_hostname => 0);
        $_camera_rest_client->getUseragent()->ssl_opts(SSL_verify_mode => SSL_VERIFY_NONE);
        $self->{_camera_rest_client} = $_camera_rest_client;
    } # if
    my $login_r = [ {cmd => "Login", param => { User => { Version => "0", userName => $camera_user_name, password => $camera_password, }}} ];
    my $response_r = _sendCameraCommand($_camera_rest_client, 'Login', $login_r, undef);
    if(defined($response_r)){
        $self->{_camera_login_token} =  @$response_r[0]->{value}->{Token}->{name};
        $self->{_camera_login_lease_time} = @$response_r[0]->{value}->{Token}->{leaseTime};
        $self->{_camera_login_lease_start_time} = time(); # detect lease expiry 
        print STDERR scalar(localtime()) . ": info: Camera Login for user '" . $self->{camera_user_name} . "' OK, token leaseTime '" . $self->{_camera_login_lease_time} . "' name '" . $self->{_camera_login_token} . "'\n" if($DEBUG > 0);
        return ($self->{_camera_login_token}, $self->{_camera_login_lease_time}); 
    }else{
        print STDERR scalar(localtime()) . ": error: Camera Login for user '" . $self->{camera_user_name}  . "' failed\n" if($DEBUG > 0);
        return (undef, undef);
    } # if
} # Login()

# Logout() - release login credentials from previous Login()
sub Logout(){
    my($self) = @_;
    my($_camera_rest_client, $_camera_login_token) = ($self->{_camera_rest_client}, $self->{_camera_login_token});
    if(defined($_camera_rest_client)){
		$self->{_camera_login_token} = undef;
		$self->{_camera_login_lease_time} = 0;
		$self->{_camera_login_lease_start_time} = time();
	}else{
        # No Login()
		return 0;
    } # if
    my $logout_r = [ {cmd => "Logout", param => { }} ];
    my $response_r = _sendCameraCommand($_camera_rest_client, 'Logout', $logout_r, $_camera_login_token);
    $self->{_camera_rest_client} = undef; # hopefully drops the REST connection
    if(defined($response_r)){
        return 1; 
    }else{
        return 0;
    } # if
} # Logout()

# GetChannelstatus() - implement camera API GetChannelstatus interface.
sub GetChannelstatus($){
    my($self) = @_;
    if(!_checkLoginLeaseTime($self)){
        return (undef, undef);
    } # if
    my $get_channelstatus_r = [ {cmd => "GetChannelstatus", } ];
    my($_camera_rest_client, $_camera_login_token) = ($self->{_camera_rest_client}, $self->{_camera_login_token});
    my $response_r = _sendCameraCommand($_camera_rest_client, 'GetChannelstatus', $get_channelstatus_r, $_camera_login_token);
    if(defined($response_r)){
        return (@$response_r[0]->{value}->{count}, @$response_r[0]->{value}->{status});
    }else{
        return (undef, undef);
    } # if
} # GetChannelstatus()

# GetDevInfo() - implement camera API GetDevInfo interface.
sub GetDevInfo($){
    my($self) = @_;
    if(!_checkLoginLeaseTime($self)){
        return undef;
    } # if
    my $get_dev_info_r = [ {cmd => "GetDevInfo", } ];
    my($_camera_rest_client, $_camera_login_token) = ($self->{_camera_rest_client}, $self->{_camera_login_token});
    my $response_r = _sendCameraCommand($_camera_rest_client, 'GetDevInfo', $get_dev_info_r, $_camera_login_token);
    if(defined($response_r)){
        return @$response_r[0]->{value}->{DevInfo};
    }else{
        return undef;
    } # if
} # GetDevInfo()

# PtzCtrl() - implement camera API PtzCtrl interface, used to control the operation of PTZ (Pan/Tilt/Zoom).
sub PtzCtrl($$$$;$){
    my($self, $channel, $op, $speed, $preset_id) = @_;
    if(!_checkLoginLeaseTime($self)){
        return 0;
    } # if
    my($_camera_rest_client, $_camera_login_token) = ($self->{_camera_rest_client}, $self->{_camera_login_token});

lib/IPCamera/Reolink.pm  view on Meta::CPAN

    if(defined($mode)){
        ${$setwhiteled_r->[0]->{param}->{WhiteLed}}{mode} = int($mode);
    } # if
    if(defined($bright)){
        ${$setwhiteled_r->[0]->{param}->{WhiteLed}}{bright} = int($bright);
    } # if
    if(defined($lighting_schedule_r)){
        ${$setwhiteled_r->[0]->{param}->{WhiteLed}}{LightingSchedule} = $lighting_schedule_r;
    } # if
    if(defined($ai_detect_type_r)){
        ${$setwhiteled_r->[0]->{param}->{WhiteLed}}{wlAiDetectType} = $ai_detect_type_r;
    } # if
    my $response_r = _sendCameraCommand($_camera_rest_client, 'SetWhiteLed', $setwhiteled_r, $_camera_login_token);
    if(defined($response_r)){
        return 1;
    }else{
        return 0;
    } # if
} # SetWhiteLed()

# GetWhiteLed() - get configuration of white led (spot light)
sub GetWhiteLed($$){
    my($self, $channel) = @_;
    if(!_checkLoginLeaseTime($self)){
        return 0;
    } # if
    my($_camera_rest_client, $_camera_login_token) = ($self->{_camera_rest_client}, $self->{_camera_login_token});
    my $getwhiteled_r = [ {cmd => "GetWhiteLed", action => 0, param => { channel => int($channel), }} ];
    my $response_r = _sendCameraCommand($_camera_rest_client, 'GetWhiteLed', $getwhiteled_r, $_camera_login_token);
    if(defined($response_r)){
        return (@$response_r[0]->{value}, @$response_r[0]->{range}, @$response_r[0]->{initial}, );
    }else{
        return (undef, undef, undef);
    } # if
} # GetWhiteLed()

=pod

=encoding UTF-8

=head1 NAME

IPCamera::Reolink - Reolink API provides access to the System, Security, Network, Video input, Enc, Record, PTZ, and Alarm functions of a Reolink IP Camera or NVR via HTTP(S)/REST

=head1 VERSION

1.10

=head1 SYNOPSIS

 use IPCamera::Reolink;

 my $camera_url = "http://192.168.1.160";
 my $camera_user_name =  "vlc"; # non-admin user recommended
 my $camera_password = 'this-is-a-bad-password';

 my $camera = IPCamera::Reolink->new($camera_url, $camera_user_name, $camera_password);
 # or
 # my $camera = IPCamera::Reolink->new( {camera_url => $camera_url_http, camera_user_name => $camera_user_name, camera_password => $camera_password, camera_x509_certificate_file => undef, camera_x509_key_file => undef, camera_certificate_authority_fi...

 # Optionally Login to the camera immmediately to validate the camera URL and credentials, otherwise a Login will be implicitly performed by the API on the first camera command.

 die "IPCamera::Reolink::Login failed" if(!$camera->Login());

 # Some camera info

 my $devinfo_r = $camera->GetDevInfo();
 print "Camera model : " . $devinfo_r->{model} . "\n";
 print "Camera name : " . $devinfo_r->{name} . "\n";

 # Camera presets

 my($ptzpreset_value_r, $ptzpreset_range_r, $ptzpreset_initial_r) = $camera->GetPtzPreset(IPCamera::Reolink::ChannelDefault);
 my $ptzpresets_r = $ptzpreset_value_r->{PtzPreset};
 foreach my $ptzpreset (@$ptzpresets_r){
     my $enable = $ptzpreset->{enable};
     if($enable){
         my $preset_id = $ptzpreset->{id};
         $camera->PtzCtrl(IPCamera::Reolink::ChannelDefault, IPCamera::Reolink::PTZ_ToPos, IPCamera::Reolink::PTZ_SpeedMax, $preset_id);
     } # if
 } # foreach

 # Start a camera pan to the left.

 $camera->PtzCtrl(IPCamera::Reolink::ChannelDefault, IPCamera::Reolink::PTZ_Left, IPCamera::Reolink::PTZ_SpeedMax);

 # Times passes, stop the last camera PTZ function.

 $camera->PtzCtrl(IPCamera::Reolink::ChannelDefault, IPCamera::Reolink::PTZ_Stop, IPCamera::Reolink::PTZ_SpeedMin);

 # Camera snapshot
 
 my $jpg_image_data = $camera->Snap(IPCamera::Reolink::ChannelDefault);
 die "IPCamera::Reolink::Snap() failed\n" if(!defined($jpg_image_data));

 my $now = time();
 my($sec, $min, $hour, $dd, $mon, $yr, $wday, $yday, $isdst) = localtime($now);
 ($dd < 10) && ($dd = "0" . $dd);
 my $mm = $mon + 1;
 ($mm < 10) && ($mm = "0" . $mm);
 ($hour < 10) && ($hour = "0" . $hour);
 my $yyyy = $yr + 1900;

 my $file_name = sprintf("%04d%02d%02d-%2s%02d%02d.jpg", $yyyy, $mm, $dd, $hour, $min, $sec);
 my $fh;
 open $fh, ">$file_name" || die("open($file_name) failed - $!");
 my $l = length($jpg_image_data);
 (syswrite($fh, $jpg_image_data, $l) == $l) || die("syswrite($file_name, $l) failed - $!\n");
 close($fh);

 # Play Audio Alarm 1 time 
 
 my $r = $camera->AudioAlarmPlay(IPCamera::Reolink::ChannelDefault, 0, 1, IPCamera::Reolink::AAP_AlarmModeTimes);

 # Turn the spotlight on.
 
 $camera->SetWhiteLed(IPCamera::Reolink::ChannelDefault, IPCamera::Reolink::WhiteLed_StateOn, undef, undef, undef, undef);

 # Turn the spotlight off.
 
 $camera->SetWhiteLed(IPCamera::Reolink::ChannelDefault, IPCamera::Reolink::WhiteLed_StateOff, undef, undef, undef, undef);

lib/IPCamera/Reolink.pm  view on Meta::CPAN

I<$IPCamera::Reolink::OSD_pos_list> is the IPCamera::Reolink::SetOsd() pos values as list.

=head2 AAP_AlarmMode_list

I<IPCamera::Reolink::AAP_AlarmMode_list> is the IPCamera::Reolink::AudioAlarmPlay() alarm_mode values as list.

=head2 IrLights_State_list

I<$IPCamera::Reolink::IrLights_State_list> is the IPCamera::Reolink::GetIrLights() and IPCamera::Reolink::SetIrLights() IrLights_State values as list.

=head1 METHODS

=head2 new({camera_url => "http://192.168.1.99/", camera_user_name => "admin", camera_password => "password", camera_x509_certificate_file => undef, camera_x509_key_file => undef, camera_certificate_authority_file => undef, })

Hash reference version.

See below for details.

=head2 new($camera_url, $camera_user_name, $camera_password)

Construct a new IPCamera::Reolink object.

Takes the following manditory parameters:

=over 4

=item $camera_url

The camera URL for access via HTTP(S), i.e. "http://192.168.1.123".

=item $camera_user_name

The camera account name for access via HTTP(S), i.e. "vlc". 

You should set up a non admin acccount for access via this API, the admin account will work as well but is not recommended.

But just to complicate things, certain methods (described below) require admin access to use with the current version of the camera firmware.

Reolink may fix this in the future.

=item $camera_password

The camera account password for access via HTTP(S), i.e. "this-is-a-bad-password". 

=item $camera_x509_certificate_file

TBD.

=item $camera_x509_key_file

TBD.

=item $camera_x509_authority_file

TBD.

=back

=head2 Login()

Login to the camera using the credentials provided to new() (above).

Upon successful Login the camera passes back a Login token and lease time that are used internally by other IPCamera::Reolink camera API methods.

The token is valid for the specified lease time.

IPCamera::Reolink will manage the Login token and will call Login() internally as needed when the Login token expires.

This should all be invisible to the caller.

=over 4

=item return

Returns (undef, undef) if the Login was rejected by the camera.

Returns ($camera_login_token, $camera_login_lease_time) if the Login is successful,
where $camera_login_token is the token passed to other API methods and $camera_login_lease_time is the time in seconds for which the token is valid, after which a new token must be aquired.

Usually the caller is not interested in these values as they are used internally by IPCamera::Reolink.

=back

=head2 Logout()

Release Login() credentials.

=over 4

=item return

Returns 1 if Logout() succeeded else 0 (zero) on failure, typically if Login() not called.

=back

=head2 GetChannelstatus()

Return camera/NVR per channel status.

=over 4

=item return

Returns ($count, $status_r) if GetChannelstatus() succeeded.

=over 4

=item $count

The number of channels in the camera/NVR, typically 1 for a camera.

The number of elements in the I<@status> array.

=item $status_r

reference to Per channel status array with I<$count> elements.

=over 4

=item $statu_r->[$i]->{channel} (0)

Channel index, 0 < $i < $count.

=item $status_r->[$i]->{name} ("CAM1")

Channel name.

=item $status_r->[$i]->{online} (1)

0 if channel offline, 1 if channel online.

=item $status_r->[$i]->{typeInfo} ("CAM1")

Channel description.

=back

=back

Returns (undef, undef)  if GetChannelstatus() failed.

=back

=head2 GetDevInfo()

Return useful information about the camera.

lib/IPCamera/Reolink.pm  view on Meta::CPAN

=back

=item $camera_preset_id 

Move camera to specfied camera preset, >= IPCamera::Reolink::PTZ_PresetMin, <= IPCamera::Reolink::PTZ_PresetMax.

Used only for IPCamera::Reolink::PTZ_ToPos camera operation.

=back

=head2 GetZoomFocus($camera_channel)

Return camera current Zoom and Focus values.

=over 4

=item $camera_channel 

Perform camera operation on specified camera channel:

=over 4

=item IPCamera::Reolink::ChannelDefault 

If you are connected to a camera then there is (usually) only 1 channel.

=item integer >= 0

If you are connected to an NVR then there is usually one channel per attached camera starting at integer value 0.

=back

=item return

Returns undef if the GetZoomFocus function failed.

Returns $zoom_focus_r hash reference to Zoom/Focus information if successful, fields most likely subject to change (typical values in parenthesis):

=over 4

=item $zoom_focus_r->{channel} (0)

Camera channel.

=item $zoom_focus_r->{focus}->{pos} (23)

Current camera focus value in the range IPCamera::Reolink::ZF_FocusPosMin, IPCamera::Reolink::ZF_FocusPosMax.

=item $zoom_focus_r->{zoom}->{pos} (0)

Current camera zoom value in the range IPCamera::Reolink::ZF_ZoomPosMin, IPCamera::Reolink::ZF_ZoomPosMax.

=back

=back

=head2 StartZoomFocus($camera_channel, $camera_operation, $camera_zoom_pos|$camera_focus_pos)

Set camera current Zoom or Focus value.

Note that the current version of the firmware on the authors camera (Firmware Version v3.1.0.2347_23061923_v1.0.0.93) requires a Login() using admin credentials to use this function.
According to Reolink this may be fixed in a future firmware version.

If in doubt, set $DEBUG to 1 and if you see a log message of the form:

Tue Dec 19 18:17:07 2023: debug: IPCamera::Reolink::_sendCameraCommand(): command 'StartZoomFocus' token '5b34aab0bb481ba' request '[{ action => 0, cmd => "StartZoomFocus", param => { ZoomFocus => { channel => 0, op => "ZoomPos", pos => 1 } } }]' res...

then you need to use admin credentials to use this function.

=over 4

=item return

Returns 1 if StartZoomFocus() succeeded else 0 (zero) on failure.

=back

=over 4

=item $camera_channel

Perform camera operation on specified camera channel.

=over 4

=item IPCamera::Reolink::ChannelDefault 

If you are connected to a camera then there is (usually) only 1 channel.

=item integer >= 0

If you are connected to an NVR then there is usually one channel per attached camera starting at integer value 0.

=back

=item $camera_operation 

Camera operation to perform.

=over 4

=item IPCamera::Reolink::ZF_ZoomPos

Set Zoom to specified pos value.

=over 4

=item $camera_zoom_pos 

Set the camera Zoom to the specified pos value in the range:

=over 4

=item IPCamera::Reolink::ZF_ZoomPosMin

Minimum camera zoom value.

=item IPCamera::Reolink::ZF_ZoomPosMax

Maximum camera zoom value.

=back

=back

=item IPCamera::Reolink::ZF_FocusPos 

Set Focus to specified pos value.

lib/IPCamera/Reolink.pm  view on Meta::CPAN


OSD display value.

=item $osd_value_r->{Osd}->{osdChannel}->{pos} ("Lower Right")

OSD display position.

=back

=item $osd_value_r->{Osd}->{osdTime}

Current OSD time display values for specified camera channel.

=over 4

=item $osd_value_r->{Osd}->{osdTime}->{enable} (0/1)

OSD time display enabled if 1 else disabled if 0.

=item $osd_value_r->{Osd}->{osdTime}->{pos} ("Lower Right")

OSD time display position.

=back

=back

=back

=item $osd_range_r

Hash reference to camera OSD value ranges.

=over 4

=item TBD

=back

=item $osd_initial_r

Hash reference to camera OSD initial values.

=over 4

=item TBD

=back

=back

=back


=head2 SetOsd($camera_channel, $enableChannel, $channelName, $channelPos, $enableTime, $timePos)

Set camera On Screen Display (OSD) values.

Note that the current version of the firmware on the authors camera
(Firmware Version v3.1.0.2347_23061923_v1.0.0.93) requires a Login()
using admin credentials to use this function.

=over 4

=item return

Returns 1 if SetOsd() succeeded else 0 (zero) on failure.

=back

=over 4

=item $camera_channel 

Perform camera operation on specified camera channel.

=over 4

=item IPCamera::Reolink::ChannelDefault 

If you are connected to a camera then there is (usually) only 1 channel.

=item integer >= 0

If you are connected to an NVR then there is usually one channel per attached camera starting at integer value 0.

=back

=item $enableChannel (0/1)

1 to enable display of channelName else 0.

=item $channelName ("rlc-823a-16x")

Channel OSD text to display

=item $channelPos (IPCamera::Reolink::OSD_UpperLeft) 

Channel OSD text position:

=over 4

=item IPCamera::Reolink::OSD_UpperLeft

OSD position "Upper Left"

=item IPCamera::Reolink::OSD_TopCenter

OSD position "Top Center"

=item IPCamera::Reolink::OSD_UpperRight

OSD position "Upper Right"

=item IPCamera::Reolink::OSD_LowerLeft

OSD position "Lower Left"

=item IPCamera::Reolink::OSD_BottomCenter

OSD position "Bottom Center"



( run in 1.032 second using v1.01-cache-2.11-cpan-39bf76dae61 )