Dahua Camera Overlay Product Name

Hello.

We Have a sorting system that sorts planks into bins. I would like a Camera to record the conveyor with an overlay on the camera showing the bin it’s on its way to.

This Url in a browser does change the overlay

http://IG:IG123456@192.168.1.108/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[0].CustomTitle[1].Text=IG%20IS%20Test

I have tried

IP = "192.168.1.108"
url = "http://"+IP+"/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[0].CustomTitle[1].Text=TESTignitionURL1"
user = 'IG'
passwd = 'IG123456'

client = system.net.httpClient(bypass_cert_validation = True,cookie_policy = "ACCEPT_ALL",redirect_policy="ALWAYS")
client.get(url,username=user, password=passwd)

I keep Getting <Response@951780780 'http://192.168.1.108/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[0].CustomTitle[1].Text=TESTignitionURL1' [401]>

I have very little experience with what I’m trying to accomplish any help would be appreciated

You'll have to provide more context, but assuming:

  1. This is Perspective
  2. You're trying to embed this camera view on the Perspective session
  3. Your camera is accessible over the network from wherever you're running these Perspective sessions

You probably want the iframe component. A script, such as system.net.httpClient, is running on the gateway, not wherever you're using the Perspective session, so whatever authentication and anything it's doing with the camera are not going to be easy to pass back to the actual front end.

I would like a script to run every time a product arrives at the first bin.

To add an overlay to the camera recording. I would like to add data to the camera recording.

I have tried to stand there with my phone and check. But the problem is so random and always comes when you not paying attention. When I’m there 400-500 planks will pass without issue.

When the complaints roll in, I can go back to the recording and see why it happened.

Got it. Ultimately what you probably want is to perform an HTTP POST to the endpoint in the URL. Open your browser's network tab and watch the requests initiated when you do this in the web browser. You will want to duplicate the (probably) POST request with to indicate that you want the server to perform an action; GET is the HTTP 'verb' for reading data, not writing it.

I have same type of camera at home I can change the overlay with windows cmd.

curl -s -g --digest -u admin:*passw* "http://111.111.111.111/cgi-bin/configManager.cgi?action=setConfig&ChannelTitle[0].Name=1234|5678

Got it. Based on the screenshot you sent me, this is in fact a GET request (somewhat odd, not the weirdest thing in the world).

The real problem doing this from Ignition scripting is going to be replicating the --digest flag. There's no built in support in the native HTTP client for digest authentication, so you'll have to script it yourself.

This LLM guided output might get you there?

# Imports for hashing, URL encoding, and random number generation
from java.security import MessageDigest, SecureRandom
from java.math import BigInteger
from urllib import urlencode
import re

# --- Helper Functions ---

def md5(text):
    """Calculates the MD5 hash of a string and returns a lowercase hex digest."""
    md = MessageDigest.getInstance("MD5")
    md.update(text.encode('utf-8'))
    digest = md.digest()
    return BigInteger(1, digest).toString(16).zfill(32)

def parse_auth_header(header_value):
    """Parses a WWW-Authenticate header string into a dictionary."""
    if header_value.lower().startswith('digest '):
        header_value = header_value[7:]
    
    # Regex to find all key="value" or key=value pairs
    parts = re.findall(r'(\w+)="([^"]*)"|(\w+)=([^\s,]+)', header_value)
    
    auth_dict = {}
    for key1, val1, key2, val2 in parts:
        if key1: # Quoted value match
            auth_dict[key1] = val1
        else: # Unquoted value match
            auth_dict[key2] = val2
            
    return auth_dict

# --- Main Script ---

# 1. Define Request Details
base_url = "http://111.111.111.111/cgi-bin/configManager.cgi"
uri_path = "/cgi-bin/configManager.cgi" # The path portion of the URL
username = "admin"
password = "*passw*" # <-- IMPORTANT: Replace with your actual password

queryParams = {
    "action": "setConfig",
    "ChannelTitle[0].Name": "1234|5678"
}

# 2. Perform Initial Request to Get the Server Challenge
client = system.net.httpClient()
initial_response = client.get(url=base_url, params=queryParams)

# 3. Extract Challenge Details from the Header
auth_header = initial_response.getHeaders().get("WWW-Authenticate")
challenge = parse_auth_header(auth_header)

realm = challenge['realm']
qop = challenge['qop']
nonce = challenge['nonce']
opaque = challenge.get('opaque', '') # Use .get() as opaque is optional

# 4. Generate Client Values and Calculate Hashes
nc = '00000001'  # Nonce count, starts at 1
cnonce = BigInteger(130, SecureRandom()).toString(16)[:16]  # Random client nonce
full_uri = uri_path + "?" + urlencode(queryParams)

# Calculate the three required hashes
HA1 = md5(username + ":" + realm + ":" + password)
HA2 = md5("GET:" + full_uri)
response_hash = md5(HA1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + HA2)

# 5. Construct the Final Authorization Header
auth_string = (
    'Digest username="{username}", realm="{realm}", nonce="{nonce}", uri="{uri}", '
    'qop={qop}, nc={nc}, cnonce="{cnonce}", response="{response}", opaque="{opaque}"'
).format(
    username=username, realm=realm, nonce=nonce, uri=full_uri, qop=qop,
    nc=nc, cnonce=cnonce, response=response_hash, opaque=opaque
)

# 6. Make the Final, Authenticated Request
# This request will throw an error if it fails, as requested.
final_response = client.get(
    url=base_url,
    params=queryParams,
    headers={'Authorization': auth_string}
)

# If the script reaches this point, the request was successful.
# You can now work with the response, for example:
print "Request successful. Response Body:"
print final_response.text

I tried it and got

Traceback (most recent call last):
File "", line 53, in
File "", line 19, in parse_auth_header
AttributeError: 'java.util.ImmutableCollections$List12' object has no attribute 'lower'

Try:

# Imports for hashing, URL encoding, and random number generation
from java.security import MessageDigest, SecureRandom
from java.math import BigInteger
from urllib import urlencode
import re

# --- Helper Functions ---

def md5(text):
    """Calculates the MD5 hash of a string and returns a lowercase hex digest."""
    md = MessageDigest.getInstance("MD5")
    md.update(text.encode('utf-8'))
    digest = md.digest()
    return BigInteger(1, digest).toString(16).zfill(32)

def parse_auth_header(header_value):
    """Parses a WWW-Authenticate header string into a dictionary."""
    if header_value.lower().startswith('digest '):
        header_value = header_value[7:]
    
    # Regex to find all key="value" or key=value pairs
    parts = re.findall(r'(\w+)="([^"]*)"|(\w+)=([^\s,]+)', header_value)
    
    auth_dict = {}
    for key1, val1, key2, val2 in parts:
        if key1: # Quoted value match
            auth_dict[key1] = val1
        else: # Unquoted value match
            auth_dict[key2] = val2
            
    return auth_dict

# --- Main Script ---

# 1. Define Request Details
base_url = "http://111.111.111.111/cgi-bin/configManager.cgi"
uri_path = "/cgi-bin/configManager.cgi" # The path portion of the URL
username = "admin"
password = "*passw*" # <-- IMPORTANT: Replace with your actual password

queryParams = {
    "action": "setConfig",
    "ChannelTitle[0].Name": "1234|5678"
}

# 2. Perform Initial Request to Get the Server Challenge
client = system.net.httpClient()
initial_response = client.get(url=base_url, params=queryParams)

# 3. Extract Challenge Details from the Header
auth_header = initial_response.headers.get("WWW-Authenticate")
challenge = parse_auth_header(auth_header[0])

realm = challenge['realm']
qop = challenge['qop']
nonce = challenge['nonce']
opaque = challenge.get('opaque', '') # Use .get() as opaque is optional

# 4. Generate Client Values and Calculate Hashes
nc = '00000001'  # Nonce count, starts at 1
cnonce = BigInteger(130, SecureRandom()).toString(16)[:16]  # Random client nonce
full_uri = uri_path + "?" + urlencode(queryParams)

# Calculate the three required hashes
HA1 = md5(username + ":" + realm + ":" + password)
HA2 = md5("GET:" + full_uri)
response_hash = md5(HA1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + HA2)

# 5. Construct the Final Authorization Header
auth_string = (
    'Digest username="{username}", realm="{realm}", nonce="{nonce}", uri="{uri}", '
    'qop={qop}, nc={nc}, cnonce="{cnonce}", response="{response}", opaque="{opaque}"'
).format(
    username=username, realm=realm, nonce=nonce, uri=full_uri, qop=qop,
    nc=nc, cnonce=cnonce, response=response_hash, opaque=opaque
)

# 6. Make the Final, Authenticated Request
# This request will throw an error if it fails, as requested.
final_response = client.get(
    url=base_url,
    params=queryParams,
    headers={'Authorization': auth_string}
)

# If the script reaches this point, the request was successful.
# You can now work with the response, for example:
print "Request successful. Response Body:"
print final_response.text

I got this now

Request successful. Response Body:
Error

Not Implemented!

:person_shrugging:

Try printing response instead of just response.text to see if there's more info.

Or hook up Wireshark and compare a successful operation from curl with what's happening when you invoke the script.

Thanks. will test everything tomorrow on site.

I have found a manual if it helps any.

3.2Authentication
The IP Camera supplies two authentication ways: basic authentication and digest authentication. Client can login through:
http:///cgi-bin/global.login?userName=admin. The IP camera returns 401. Then the client inputs a username and password to authorize.
For example:

  1. When basic authentication, the IP camera response:
    401 Unauthorized
    WWW-Authenticate: Basic realm=”XXXXXX”
    Then the client encode the username and password with base64, send the following request:
    Authorization: Basic VXZVXZ.
  2. When digest authentication, the IP camera response:
    WWW-Authenticate: Digest realm="DH_00408CA5EA04", nonce="000562fdY631973ef04f77a3ede7c1832ff48720ef95ad",
    stale=FALSE, qop="auth";
    The client calculates the digest using username, password, nonce, realm and URI with MD5, then send the following request:
    Authorization: Digest username="admin", realm="DH_00408CA5EA04", nc=00000001,cnonce="0a4f113b",qop="auth"
    nonce="000562fdY631973ef04f77a3ede7c1832ff48720ef95ad",uri="cgi-bin/global.login?userName=admin",
    response="65002de02df697e946b750590b44f8bf"

Here is something else I found

Dahua API | IP Cam Talk

import hashlib
import requests
from requests.auth import HTTPDigestAuth

# Parameters
username = "admin"
password = "autvix123456"
url = "http://192.168.1.108/cgi-bin/magicBox.cgi?action=getLanguageCaps%20HTTP/1.1"

# Get the initial challenge
response = requests.get(url, auth=HTTPDigestAuth(username, password), verify=False)

# Check if the challenge requires digest authentication
if response.status_code == 401:
    headers = response.headers
    auth_header = headers.get('www-authenticate', '')
    
    # Extract parameters from the WWW-Authenticate header
    # This parsing might need adjustment based on the actual header format
    import re
    match = re.search(r'realm="([^"]+)"', auth_header)
    realm = match.group(1) if match else ""
    match = re.search(r'nonce="([^"]+)"', auth_header)
    nonce = match.group(1) if match else ""
    match = re.search(r'qop="([^"]+)"', auth_header)
    qop = match.group(1) if match else ""
    
    # Generate HA1, HA2, and Response
    HA1 = hashlib.md5(f"{username}:{realm}:{password}".encode()).hexdigest()
    HA2 = hashlib.md5(f"GET:{url}".encode()).hexdigest()
    response_digest = hashlib.md5(f"{HA1}:{nonce}:00000001:0a4f113b:{qop}:{HA2}".encode()).hexdigest()
    
    # Send the authenticated request
    headers = {
        'Authorization': f'Digest username="{username}", realm="{realm}", nonce="{nonce}", uri="{url}", response="{response_digest}", qop="{qop}", nc=00000001, cnonce="0a4f113b"'
    }
    final_response = requests.get(url, headers=headers, verify=False)
    
    if final_response.status_code == 200:
        print("Access successful!")
        print(final_response.json())
    else:
        print(f"Failed to access the URL. Status code: {final_response.status_code}")
else:
    print(response.content)
    print(f"Failed to obtain challenge. Status code: {response.status_code}")

Also On the forum

How to use digest authentication in httpBinding - Ignition - Inductive Automation Forum

>>> 
Request successful. Response Body:
<Response@2100990679 'http://10.0.3.103/cgi-bin/configManager.cgi?ChannelTitle%5B0%5D.Name=1234%7C5678&action=setConfig' [400]>
Error

Bad Request!


>>> 

http://10.0.3.103/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[0].CustomTitle[1].Text=Testing

queryParams = {
    "action": "setConfig",
    "VideoWidget[0].UserDefinedTitle[0].Text": PlankName

Its Working Now Thanks for the help

1 Like