Reolink RLC-411WS API (non-ONVIF)

If you've made a patch to quick fix a bug or to add a new feature not yet in the main tree then post it here so others can try it out.
Post Reply
adamoutler
Posts: 5
Joined: Sun Apr 22, 2018 2:45 pm

Reolink RLC-411WS API (non-ONVIF)

Post by adamoutler »

Hi,
I picked up two Reolink RLC-411WS 4K cameras with Zoom/Focus for $125 from Amazon https://www.amazon.com/gp/product/B019MMAE3O and I found the user contribution here for Reolink. https://github.com/ZoneMinder/zoneminde ... Reolink.pm. However, this ONVIF method did not work on my camera.

So I implemented the Reolink API, which is used by their web interface, for the focus/zoom/reset capabilities on this device.
Features
  • Zoom In Continuous
  • Zoom Out Continuous
  • Zoom Stop
  • Focus near continuous
  • Focus Wide continuous
  • Focus Stop
  • Reset Camera

Setup
Find the Perl Procedural Module here: https://pastebin.adamoutler.com/oKr0
Save the PM file as /usr/share/perl5/ZoneMinder/Control/ReolinkHTTP.pm
Control Compatibility: type:"FFMPEG", protocol: "ReolinkHTTP" options: "Can Reset" "Can Zoom", "Can Zoom Continuous", "Can Focus", "Can Focus Continuous"
Control Device: "userName":"admin","password":"" (Out-of-object JSON format "username":"my username","password","MYPASSWORD". These quotes are important. This is the only place you need the quotes in this post)
Control Address: 10.10.187.101 (the IP address)

About
This utilizes HTTP POST requests to send a JSON object to the device. I ran into problems at first because the web UI utilizes a token system in order to validate the requests. However, through experimentation I was able to find that you can send a JSON array such as [{login},{action}] to the device and it accepts the first packet as login and the second as a proper action.

Note: because this is sending a username and password with every request, you should use this only on a private network, or with HTTPS between the camera and the server. This is only slightly worse than basic auth, because basic auth at least base64s the username/password. This is a username and password in a plain-text packet

here is an example "ZoomInc" (zoom in) packet which zoneminder will send to cam.e.ra.IP/api.cgi?cmd=Login

Code: Select all

[{"cmd":"Login","action":0,"param":{"User":{"userName":"admin","password":""}}},{"cmd":"PtzCtrl","action":0,"param":{"channel":0,"op":"ZoomInc","speed":32}}]
You can use the camera's web APIs through curl with the following curl command

Code: Select all

curl -d '[{"cmd":"Login","action":0,"param":{"User":{"userName":"admin","password":""}}},{"cmd":"PtzCtrl","action":0,"param":{"channel":0,"op":"ZoomInc","speed":32}}]' 10.10.187.102/api.cgi?cmd=Login
Here is an example response

Code: Select all

[
   {
      "cmd" : "Login",
      "code" : 0,
      "value" : {
         "Token" : {
            "leaseTime" : 3600,
            "name" : "0865a3fb928c189"
         }
      }
   },
   {
      "cmd" : "PtzCtrl",
      "code" : 0,
      "value" : {
         "rspCode" : 200
      }
   }
]
I found it very convenient that we could simply send a login and a command as a single request in a JSON array. We now have support for Reolink's Web API for those who cannot get it to work through ONVIF such as myself. So i hope this helps someone!
adamoutler
Posts: 5
Joined: Sun Apr 22, 2018 2:45 pm

Re: Reolink RLC-411WS API (non-ONVIF)

Post by adamoutler »

Link went down. I'm posting it here.

Code: Select all

adamoutler@camserver:/usr/share/perl5/ZoneMinder/Control$ cat ReolinkHTTP.pm
# ==========================================================================
#
# ZoneMinder Reolink RLC-411WS Web API Control Protocol Module
# Copyright (C) 2014  Philip Coombes
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================
#


package ZoneMinder::Control::ReolinkHTTP;

use 5.006;
use strict;
use warnings;
use Data::Dumper;

require ZoneMinder::Base;
require ZoneMinder::Control;

our @ISA = qw(ZoneMinder::Control);

# ==========================================================================
#
# Reolink HTTP API Control Protocol
#
# ==========================================================================

use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);

use Time::HiRes qw( usleep );
use URI::Encode qw();

sub new {
    my $class = shift;
    my $id = shift;
    my $self = ZoneMinder::Control->new( $id );
    bless( $self, $class );
    srand( time() );
    return $self;
}

our $AUTOLOAD;

sub AUTOLOAD {
    my $self = shift;
    my $class = ref($self) || croak( "$self not object" );
    my $name = $AUTOLOAD;
    $name =~ s/.*://;
    if ( exists($self->{$name}) )
    {
        return( $self->{$name} );
    }
    Fatal( "Can't access $name member of object of class $class" );
}

sub open {
    my $self = shift;

    $self->loadMonitor();

    use LWP::UserAgent;
    $self->{ua} = LWP::UserAgent->new;
    $self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::VERSION );

    $self->{state} = 'open';
}

sub close {
    my $self = shift;
    $self->{state} = 'closed';
}

sub sendCmd {
    my $self = shift;
    my $cmd = shift;
    my $result = undef;
    my $url;
    if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) {
        $url = $self->{Monitor}->{ControlAddress}
            .'/api.cgi?cmd=Login';
        Error("$url");
    } else {
        $url = 'http://'.$self->{Monitor}->{ControlAddress}
            .'/api.cgi?cmd=Login';
        Error("$url");
    } # en dif
    my $uri     = URI::Encode->new( { encode_reserved => 0 } );
    my $encoded = $uri->encode( $cmd );
    my $res = $self->{ua}->post( $url,  'Content-Type'=>"Content-Type=text/html", 'Content'=>"$cmd" );
    if ( $res->is_success ) {
        $result = !undef;
    } else {
        Error( "Error check failed: '".$res->status_line()."'" );
    }

    return( $result );
}



sub reset {
    my $self = shift;
    Debug( "Camera Reset" );
    $self->sendCmd( '[{"cmd":"Login","action":0,"param":{"User":{'.$self->{Monitor}->{ControlDevice}.'}}},{"cmd":"Reboot","action":0,"param":{}}]' );
}


sub zoomConWide
{
    my $self = shift;
    Debug( "Zoom Wide $self" );
    $self->sendCmd( '[{"cmd":"Login","action":0,"param":{"User":{'.$self->{Monitor}->{ControlDevice}.'}}},{"cmd":"PtzCtrl","action":0,"param":{"channel":0,"op":"ZoomDec","speed":10}}]' );
}
sub zoomStop
{
    my $self = shift;
    Debug( "Zoom Wide $self" );
    $self->sendCmd( '[{"cmd":"Login","action":0,"param":{"User":{'.$self->{Monitor}->{ControlDevice}.'}}},{"cmd":"PtzCtrl","action":0,"param":{"channel":0,"op":"Stop"}}]' );

}
sub zoomConTele
{
    my $self = shift;
    Debug( "Zoom Wide $self" );
    $self->sendCmd( '[{"cmd":"Login","action":0,"param":{"User":{'.$self->{Monitor}->{ControlDevice}.'}}},{"cmd":"PtzCtrl","action":0,"param":{"channel":0,"op":"ZoomInc","speed":10}}]' );
}
sub focusConFar
{
    my $self = shift;
    Debug( "Zoom Wide $self" );
    $self->sendCmd( '[{"cmd":"Login","action":0,"param":{"User":{'.$self->{Monitor}->{ControlDevice}.'}}},{"cmd":"PtzCtrl","action":0,"param":{"channel":0,"op":"FocusInc","speed":10}}]' );
}
sub focusStop
{
    my $self = shift;
    Debug( "Zoom Wide $self" );
    $self->sendCmd( '[{"cmd":"Login","action":0,"param":{"User":{'.$self->{Monitor}->{ControlDevice}.'}}},{"cmd":"PtzCtrl","action":0,"param":{"channel":0,"op":"Stop"}}]' );
}
sub focusConNear
{
    my $self = shift;
    Debug( "Zoom Wide $self" );
    $self->sendCmd( '[{"cmd":"Login","action":0,"param":{"User":{'.$self->{Monitor}->{ControlDevice}.'}}},{"cmd":"PtzCtrl","action":0,"param":{"channel":0,"op":"FocusDec","speed":10}}]' );
}



1;
nrbell
Posts: 30
Joined: Mon Jun 04, 2018 1:14 pm

Re: Reolink RLC-411WS API (non-ONVIF)

Post by nrbell »

This is interesting, and thank you for doing it, but I had trouble getting it working. I also have a RLC-411WS and would at least like to control the zoom. I was able to use your script, but it didn't quite act as I expected. Whenever I zoomed, it would zoom to completion in the direction I was zooming. If I said "zoom in" it would zoom all the way in. The same was true for zooming out. I assume I'm doing something wrong. How do I only get it to zoom only a little bit when I ask it to zoom?
Post Reply