iDS-2CD7A26G0/P-IZHS get captured plate numbers via ISAPI or just API?

shakir

n3wb
Feb 20, 2023
2
2
Uzbekistan
I'm using a iDS-2CD7A26G0/P-IZHS ANPR to capture plate-numbers of cars who visited carwash center. What I want now is that, I want to get the captured-plates-result (ex: 'plate-number', 'time-stamp'...) through ISAPI or calling some API and insert it to my MYSQL database.
I followed this: stackoverfow post, but I dont know why, it didnt work. It returns 401 error.
bellow is the python source code that I used to get the latest capture results.

Python:
import requests
from requests.auth import HTTPDigestAuth
import xml.etree.ElementTree as ET

endpoint = "http://192.168.0.64/ISAPI/Traffic/channels/1/vehicleDetect/results"
headers = {'Content-Type': 'application/xml'}
auth1 = ('user', 'q1234!@#$')
auth2 = requests.auth.HTTPDigestAuth('user', 'q1234!@#$')

# Send an HTTP GET request to the endpoint
response1 = requests.get(endpoint, headers=headers, auth=auth1)
print(response1.status_code)
response2 = requests.get(endpoint, headers=headers, auth=auth2)
print(response2.status_code)

# Parse the response XML to retrieve the detected plate number
if response1.status_code == 200:
    root = ET.fromstring(response1.content)
    plate_number = root.find('./PlateResult').get('license')
    confidence = root.find('./PlateResult').get('confidence')
    plate_location = root.find('./PlateResult').get('plateLocation')

# Parse the response XML to retrieve the detected plate number
if response2.status_code == 200:
    root = ET.fromstring(response2.content)
    plate_number = root.find('./PlateResult').get('license')
    confidence = root.find('./PlateResult').get('confidence')
    plate_location = root.find('./PlateResult').get('plateLocation')
 
Last edited:
  • Like
Reactions: oybek94 and Otabek
Digest Auth POST Body:

<?xml version="1.0" encoding="UTF-8"?>
<AfterTime version="2.0" xmlns="">
<picTime>YYYYMMDDHHMMSS</picTime>
</AfterTime>


This will give you 20 results and that is max and the starting time is set in picTime

As you can see you were doing a GET which would get last 20 always, now you do post with starting time cause it contains more data now.

Cheers


Hi, thanks for your response.
Tried it, butIt didn't work for me. It is returning 401
 
I have noticed your Body formatting is horrible. Its probably copy paste typo. Please format it as xml should look like. That is cause of your problem
 
  • Like
Reactions: aqeel
I'm using a iDS-2CD7A26G0/P-IZHS ANPR to capture plate-numbers of cars who visited carwash center. What I want now is that, I want to get the captured-plates-result (ex: 'plate-number', 'time-stamp'...) through ISAPI or calling some API and insert it to my MYSQL database.
I followed this: stackoverfow post, but I dont know why, it didnt work. It returns 401 error.
bellow is the python source code that I used to get the latest capture results.

Python:
import requests
from requests.auth import HTTPDigestAuth
import xml.etree.ElementTree as ET

endpoint = "http://192.168.0.64/ISAPI/Traffic/channels/1/vehicleDetect/results"
headers = {'Content-Type': 'application/xml'}
auth1 = ('user', 'q1234!@#$')
auth2 = requests.auth.HTTPDigestAuth('user', 'q1234!@#$')

# Send an HTTP GET request to the endpoint
response1 = requests.get(endpoint, headers=headers, auth=auth1)
print(response1.status_code)
response2 = requests.get(endpoint, headers=headers, auth=auth2)
print(response2.status_code)

# Parse the response XML to retrieve the detected plate number
if response1.status_code == 200:
    root = ET.fromstring(response1.content)
    plate_number = root.find('./PlateResult').get('license')
    confidence = root.find('./PlateResult').get('confidence')
    plate_location = root.find('./PlateResult').get('plateLocation')

# Parse the response XML to retrieve the detected plate number
if response2.status_code == 200:
    root = ET.fromstring(response2.content)
    plate_number = root.find('./PlateResult').get('license')
    confidence = root.find('./PlateResult').get('confidence')
    plate_location = root.find('./PlateResult').get('plateLocation')
you can check this post:
 
  • Like
Reactions: trempa92
@paulpeter Thats realtime fetch via Alarm server. And its best possible way to fetch. But i think ISAPI is needed as a backup for the network lost time if the server isn't running 24/7. I don't believe ANR will solve longer network loss

C# equivalent:

Code:
using Microsoft.Extensions.Hosting;
using Microsoft.SqlServer.Server;
using ProxyApi.db_cotroller;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;

public class TcpServerService : BackgroundService
{
    private TcpListener _tcpListener;

    private readonly IWebHostEnvironment _env;

    public TcpServerService(IWebHostEnvironment env)
    {
        _env = env;
    }
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        string localIP = GetLocalIPAddress();
        _tcpListener = new TcpListener(IPAddress.Parse(localIP), 9091); / Adjust the IP and port
        _tcpListener.Start();

        Console.ForegroundColor = ConsoleColor.Green;

        Console.WriteLine($"HTTP LISTENER started listening on {localIP}:9091.");

        Console.ResetColor();

        while (!stoppingToken.IsCancellationRequested)
        {
            TcpClient client = await _tcpListener.AcceptTcpClientAsync();
            _ = HandleClient(client);
        }
    }

    public static string GetLocalIPAddress()
    {
        var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();

        foreach (var networkInterface in networkInterfaces)
        {
            if (networkInterface.NetworkInterfaceType != NetworkInterfaceType.Wireless80211 &&
                networkInterface.NetworkInterfaceType != NetworkInterfaceType.Loopback)
            {
                var ipProperties = networkInterface.GetIPProperties();
                foreach (var ip in ipProperties.UnicastAddresses)
                {
                    if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
                    {
                        return ip.Address.ToString();
                    }
                }
            }
        }

        throw new Exception("No Ethernet network adapters with an IPv4 address in the system!");
    }


    private async Task HandleClient(TcpClient client)
    {
        string clientIP = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine($"HTTP LISTENER: {clientIP} sending data.");
        Console.ResetColor();

        try
        {
            using (NetworkStream stream = client.GetStream())
            {
                MemoryStream memoryStream = new MemoryStream();
                byte[] buffer = new byte[client.ReceiveBufferSize];
                int bytesRead;

                while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                {
                    memoryStream.Write(buffer, 0, bytesRead);
                }

                byte[] requestData = memoryStream.ToArray();

                string boundaryString = GetBoundary(requestData);

                if (boundaryString == null)
                {
                    Console.WriteLine("Boundary string not found in the request.");
                    return;
                }

                List<MultipartFormData> formDataList = ExtractFormData(requestData, boundaryString);

                foreach (var formData in formDataList)
                {

                    if (formData.IsImage)
                    {
                        string webRootPath = _env.WebRootPath;
                        string folderPath = Path.Combine(webRootPath, "images", "lpr_images");

                        Directory.CreateDirectory(folderPath);
                        string filePath = Path.Combine(folderPath, formData.Filename);
                        File.WriteAllBytes(filePath, formData.Content);
                        Console.WriteLine($"IMAGE: Saved {formData.Filename}");
                    }
                    else if (formData.ContentType.Equals("text/xml", StringComparison.OrdinalIgnoreCase))
                    {
                        string xmlContent = Encoding.UTF8.GetString(formData.Content);

                        XNamespace ns = "http://www.hikvision.com/ver20/XMLSchema";
                        XDocument xmlDoc = XDocument.Parse(xmlContent);

                        string eventType = xmlDoc.Root.Element(ns + "eventType")?.Value;
                        if (!string.Equals(eventType, "ANPR", StringComparison.OrdinalIgnoreCase))
                        {
                            Console.WriteLine("Skipping non-ANPR event.");
                            continue;
                        }

                        string ipAddress = xmlDoc.Root.Element(ns + "ipAddress")?.Value;
                        string dateTime = xmlDoc.Root.Element(ns + "dateTime")?.Value;
                        XElement anprElement = xmlDoc.Root.Element(ns + "ANPR");
                        string licensePlate = anprElement?.Element(ns + "licensePlate")?.Value;
                        string vehicleListName = anprElement?.Element(ns + "vehicleListName")?.Value;
                        string secondPIdValue = "";

                        if (anprElement != null)
                        {
                            XElement pictureInfoListElement = anprElement.Element(ns + "pictureInfoList");

                            if (pictureInfoListElement != null)
                            {
                                / Select the second pId element by index (index 1) within the ANPR element
                                XElement secondPictureInfoElement = pictureInfoListElement.Elements(ns + "pictureInfo").ElementAtOrDefault(1);

                                if (secondPictureInfoElement != null)
                                {
                                    XElement secondPIdElement = secondPictureInfoElement.Element(ns + "pId");

                                    if (secondPIdElement != null)
                                    {
                                        secondPIdValue = secondPIdElement.Value + ".jpg";
                                    }
                                }
                            }

                            / Now you have the secondPIdValue                          
                        }

                        if (ipAddress == null || dateTime == null || licensePlate == null || vehicleListName == null || secondPIdValue == null)
                        {
                            Console.WriteLine("One or more required XML elements are missing.");
                            continue;
                        }                    
                        SaveLPRlogstoDB.SaveLprLogs(ipAddress, dateTime, licensePlate, vehicleListName, secondPIdValue);
                    }
                }

                byte[] responseBytes = Encoding.ASCII.GetBytes("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
                await stream.WriteAsync(responseBytes, 0, responseBytes.Length);
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("HTTP LISTENER: Data processed and response sent.");
                Console.ResetColor();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An error occurred while handling the client: {ex}");
        }
    }

    static string GetBoundary(byte[] requestData)
    {
        string header = Encoding.UTF8.GetString(requestData);
        Match match = Regex.Match(header, "boundary=(?<boundary>.+?)\r\n");
        if (match.Success)
        {
            return "--" + match.Groups["boundary"].Value.Trim();
        }
        return null;
    }

    static List<MultipartFormData> ExtractFormData(byte[] requestData, string boundaryString)
    {
        List<MultipartFormData> formDataList = new List<MultipartFormData>();
        byte[] boundaryBytes = Encoding.ASCII.GetBytes(boundaryString);

        int currentIndex = 0;
        while (currentIndex < requestData.Length)
        {
            int headerStartIndex = FindIndex(requestData, Encoding.ASCII.GetBytes("Content-Disposition: form-data;"), currentIndex);
            if (headerStartIndex == -1) break;

            int headerEndIndex = FindIndex(requestData, Encoding.ASCII.GetBytes("\r\n\r\n"), headerStartIndex);
            if (headerEndIndex == -1) break;

            string header = Encoding.ASCII.GetString(requestData, headerStartIndex, headerEndIndex - headerStartIndex);
            var filenameMatch = Regex.Match(header, "filename=\"(?<filename>[^\"]+)\"");
            var contentTypeMatch = Regex.Match(header, "Content-Type: (?<contentType>[^\r\n]+)");

            string filename = filenameMatch.Groups["filename"].Value;
            string contentType = contentTypeMatch.Success ? contentTypeMatch.Groups["contentType"].Value : "application/octet-stream"; / Default content type if none specified

            int contentStartIndex = headerEndIndex + 4; / The start index of the content data
            int boundaryIndex = FindIndex(requestData, boundaryBytes, contentStartIndex);
            if (boundaryIndex == -1) boundaryIndex = requestData.Length;

            int contentLength = boundaryIndex - contentStartIndex;
            byte[] contentData = new byte[contentLength];
            Array.Copy(requestData, contentStartIndex, contentData, 0, contentLength);

            formDataList.Add(new MultipartFormData
            {
                ContentType = contentType,
                Filename = filename,
                Content = contentData
                / IsImage is a computed property based on ContentType; no need to set it explicitly
            });

            currentIndex = boundaryIndex + boundaryBytes.Length; / Set currentIndex to after the boundary for the next loop iteration
        }

        return formDataList;
    }

    static int FindIndex(byte[] data, byte[] pattern, int startIndex = 0)
    {
        for (int i = startIndex; i < data.Length - pattern.Length + 1; i++)
        {
            bool isMatch = true;
            for (int j = 0; j < pattern.Length; j++)
            {
                if (data[i + j] != pattern[j])
                {
                    isMatch = false;
                    break;
                }
            }
            if (isMatch) return i;
        }
        return -1;
    }



    class MultipartFormData
    {
        public string ContentType { get; set; }
        public string Filename { get; set; }
        public byte[] Content { get; set; }
        public bool IsImage => ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase);
    }



    public override Task StopAsync(CancellationToken cancellationToken)
    {
        _tcpListener?.Stop();
        return base.StopAsync(cancellationToken);
    }
}
 
  • Like
Reactions: aqeel
Hi Everyone and @trempa92
have you ever encountered an issue like this?

1714131861022.png

I'm trying to register the allowlist for the new license plate using ISAPI on the DS-TCG405-E LPR Camera series.
It successfully went through, but the output is showing strange characters.

Warm regards,
Rian
 
perhaps unicode escaping problem? What is original data?

I havent worked with TCG serie as their trigger percentage is way lower than serie 7 camera. So i stuck with serie 7 which is great and never had such problem there.

Were you using POSTMAN for this request or directly from your app?
 
Have you tried looking for a newer firmware? Perhaps it will solve the problem.


1714375036156.png
Serie 7 camera have different body and url

/ISAPI/Traffic/channels/1/licensePlateAuditData/record?format=json


1714374786765.png

Good luck!
 
Hi @trempa92

Finally got it.

1714382283450.png


/ISAPI/Traffic/channels/1/licensePlateAuditData/record?format=json

1714382334792.png

Can share how to fetch real-time vehicle number data / picture?

Big Thanks for your assistance,
 
  • Like
Reactions: Otabek