From 8b09decf8826230e8c1782230a215d89f6b0a0de Mon Sep 17 00:00:00 2001 From: Mark McIntyre Date: Thu, 29 Apr 2021 22:51:33 +0100 Subject: [PATCH 1/3] Reworked camera configuration tools --- Scripts/RMS_SetCameraParams.ps1 | 94 ------------ Scripts/RMS_SetCameraParams.sh | 61 ++------ Utils/SetCameraAddress.py | 248 ++++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+), 143 deletions(-) delete mode 100644 Scripts/RMS_SetCameraParams.ps1 create mode 100644 Utils/SetCameraAddress.py diff --git a/Scripts/RMS_SetCameraParams.ps1 b/Scripts/RMS_SetCameraParams.ps1 deleted file mode 100644 index c0ee48225..000000000 --- a/Scripts/RMS_SetCameraParams.ps1 +++ /dev/null @@ -1,94 +0,0 @@ -# -# powershell script to set an IMX291 camera up from scratch -# -if ( $args.count -eq 0 ){ - Write-Output "usage1 python -m Utils.CameraControl DIRECT" - Write-Output " configure the camera for direct connection to the pi" - Write-Output "usage1 python -m Utils.CameraControl targetipaddress routeripaddress" - Write-Output " configure the camera for connection via your router" - write-output " the two parameters are the IP address you want the camera to have" - write-output " and the address of your router." - exit 1 -} - -$currip=$(python -m Utils.CameraControl GetIP) -if ($args[0] -eq "DIRECT"){ - write-output Setting direct connection - write-output Warning: you will lose connection to the camera once this completes -}else{ - if ($args.count -lt 2){ - write-output "direct mode requires you to provide a Camera IP address and your routers IP address" - exit 1 - } - write-output "Setting via-router connection" - $camip=$args[0] - $routerip=$args[1] -} -write-output "------------------------" -# a few miscellaneous things - onscreen date/camera Id off, colour settings, autoreboot at 1500 every day -python -m Utils.CameraControl SetOSD off -python -m Utils.CameraControl SetColor 100,50,50,50,0,0 -python -m Utils.CameraControl SetAutoReboot Everyday,15 - -# set the Video Encoder parameters -python -m Utils.CameraControl SetParam Encode Video Compression H.264 -python -m Utils.CameraControl SetParam Encode Video Resolution 720P -python -m Utils.CameraControl SetParam Encode Video BitRateControl VBR -python -m Utils.CameraControl SetParam Encode Video FPS 25 -python -m Utils.CameraControl SetParam Encode Video Quality 6 -python -m Utils.CameraControl SetParam Encode AudioEnable 0 -python -m Utils.CameraControl SetParam Encode VideoEnable 1 -python -m Utils.CameraControl SetParam Encode SecondStream 0 - -# camera parameters -python -m Utils.CameraControl SetParam Camera Style type1 -python -m Utils.CameraControl SetParam Camera AeSensitivity 1 -python -m Utils.CameraControl SetParam Camera ApertureMode 0 -python -m Utils.CameraControl SetParam Camera BLCMode 0 -python -m Utils.CameraControl SetParam Camera DayNightColor 2 -python -m Utils.CameraControl SetParam Camera Day_nfLevel 0 -python -m Utils.CameraControl SetParam Camera DncThr 50 -python -m Utils.CameraControl SetParam Camera ElecLevel 100 -python -m Utils.CameraControl SetParam Camera EsShutter 0 -python -m Utils.CameraControl SetParam Camera ExposureParam LeastTime 40000 -python -m Utils.CameraControl SetParam Camera ExposureParam Level 0 -python -m Utils.CameraControl SetParam Camera ExposureParam MostTime 40000 -python -m Utils.CameraControl SetParam Camera GainParam AutoGain 1 -python -m Utils.CameraControl SetParam Camera GainParam Gain 60 -python -m Utils.CameraControl SetParam Camera IRCUTMode 0 -python -m Utils.CameraControl SetParam Camera IrcutSwap 0 -python -m Utils.CameraControl SetParam Camera Night_nfLevel 0 -python -m Utils.CameraControl SetParam Camera RejectFlicker 0 -python -m Utils.CameraControl SetParam Camera WhiteBalace 2 -python -m Utils.CameraControl SetParam Camera PictureFlip 0 -python -m Utils.CameraControl SetParam Camera PictureMirror 0 - -# network parameters -python -m Utils.CameraControl SetParam Network EnableDHCP 0 -python -m Utils.CameraControl SetParam Network TransferPlan Fluency - -write-output "------------------------" -write-output "about to update the camera IP address. You will see a timeout message" -if ($args[0] -eq "DIRECT"){ - python -m Utils.CameraControl SetParam Network GateWay 192.168.42.1 - python -m Utils.CameraControl SetParam Network HostIP 192.168.42.10 - python -m Utils.CameraControl SetParam Network EnableDHCP 1 -}else{ - python -m Utils.CameraControl SetParam Network GateWay $routerip - python -m Utils.CameraControl SetParam Network HostIP $camip -} -write-output "------------------------" -write-output "updating config file" -(Get-Content .config).replace("$currip", "$camip") | Set-Content tmp.tmp -Move-Item .config .config.orig -force -Move-Item tmp.tmp .config -write-output "------------------------" -write-output "the camera will now reboot.... " -Start-Sleep 5 - -if ($args[0] -eq "DIRECT"){ - write-output "now plug the camera into the Pi" -}else{ - $camip=$(python -m Utils.CameraControl GetIP) - write-output "Camera ip is now $camip" -} \ No newline at end of file diff --git a/Scripts/RMS_SetCameraParams.sh b/Scripts/RMS_SetCameraParams.sh index 9e60709d7..e1acdf425 100755 --- a/Scripts/RMS_SetCameraParams.sh +++ b/Scripts/RMS_SetCameraParams.sh @@ -2,30 +2,20 @@ # # bash script to set an IMX291 camera up from scratch # -if [ $# -lt 1 ] ; then - echo "usage1 python -m Utils.CameraControl DIRECT" - echo " configure the camera for direct connection to the pi" - echo "usage1 python -m Utils.CameraControl targetipaddress routeripaddress" - echo " configure the camera for connection via your router" - echo " the two parameters are the IP address you want the camera to have" - echo " and the address of your router." - exit 1 -fi +echo "This script will set your camera to the recommended settings" +echo "for brightness, video style, gain, and so on. " +echo "" +echo "NB: The script requires that your camera is -already- set to the " +echo "right IP address and that this address has been added to the RMS .config file." +echo "" +echo "If you have not yet configured the camera IP address, press Ctrl-C. " +echo "" +echo "otherwise press any key to continue." +read goonthen currip=$(python -m Utils.CameraControl GetIP) -if [ "$1" == "DIRECT" ] ; then - echo Setting direct connection - echo Warning: you will lose connection to the camera once this completes -else - if [ $# -lt 2 ] ; then - echo direct mode requires you to provide a Camera IP address and your routers IP address - exit 1 - fi - echo Setting via-router connection - camip=$1 - routerip=$2 -fi -echo "------------------------" + +echo "Camera Address is $currip" # a few miscellaneous things - onscreen date/camera Id off, colour settings, autoreboot at 1500 every day python -m Utils.CameraControl SetOSD off @@ -66,32 +56,5 @@ python -m Utils.CameraControl SetParam Camera PictureFlip 0 python -m Utils.CameraControl SetParam Camera PictureMirror 0 # network parameters -python -m Utils.CameraControl SetParam Network EnableDHCP 0 python -m Utils.CameraControl SetParam Network TransferPlan Fluency -echo "------------------------" -echo "about to update the camera IP address. You will see a timeout message" -if [ "$1" == "DIRECT" ] ; then - python -m Utils.CameraControl SetParam Network GateWay 192.168.42.1 - python -m Utils.CameraControl SetParam Network HostIP 192.168.42.10 - python -m Utils.CameraControl SetParam Network EnableDHCP 1 -else - python -m Utils.CameraControl SetParam Network GateWay $routerip - python -m Utils.CameraControl SetParam Network HostIP $camip -fi -echo "------------------------" -echo "updating config file" -cat .config | sed "s/$currip/$camip/g" > tmp.tmp -mv .config .config.orig -mv tmp.tmp .config - -echo "------------------------" -echo "the camera will now reboot.... " -sleep 5 - -if [ "$1" == "DIRECT" ] ; then - echo "now plug the camera into the Pi" -else - currip=$(python -m Utils.CameraControl GetIP) - echo Camera ip is now $currip -fi \ No newline at end of file diff --git a/Utils/SetCameraAddress.py b/Utils/SetCameraAddress.py new file mode 100644 index 000000000..7c6007f01 --- /dev/null +++ b/Utils/SetCameraAddress.py @@ -0,0 +1,248 @@ +import struct +import json +import hashlib +import threading +from socket import socket, AF_INET, SOCK_STREAM, SOCK_DGRAM +import time +import logging +import sys +import binascii +import ipaddress as ip +import socket as mysocket + + +def strIPtoHex(ip): + a = binascii.hexlify(mysocket.inet_aton(ip)).decode().upper() + addr='0x'+''.join([a[x:x+2] for x in range(0,len(a),2)][::-1]) + return addr + + +def iptoString(s): + a=s[2:] + addr='0x'+''.join([a[x:x+2] for x in range(0,len(a),2)][::-1]) + ipaddr=ip.IPv4Address(int(addr,16)) + return ipaddr + + +class DVRIPCam(object): + DATE_FORMAT = "%Y-%m-%d %H:%M:%S" + OK_CODES = [100, 515] + PORTS = { + "tcp": 34567, + "udp": 34568, + } + + def __init__(self, ip, **kwargs): + self.logger = logging.getLogger(__name__) + self.ip = ip + self.user = kwargs.get("user", "admin") + hash_pass = kwargs.get("hash_pass") + self.hash_pass = hash_pass or self.sofia_hash(kwargs.get("password", "")) + self.proto = kwargs.get("proto", "tcp") + self.port = kwargs.get("port", self.PORTS.get(self.proto)) + self.socket = None + self.packet_count = 0 + self.session = 0 + self.alive_time = 20 + self.alive = None + self.alarm = None + self.alarm_func = None + self.busy = threading.Condition() + + def connect(self, timeout=10): + if self.proto == "tcp": + self.socket_send = self.tcp_socket_send + self.socket_recv = self.tcp_socket_recv + self.socket = socket(AF_INET, SOCK_STREAM) + self.socket.connect((self.ip, self.port)) + elif self.proto == "udp": + self.socket_send = self.udp_socket_send + self.socket_recv = self.udp_socket_recv + self.socket = socket(AF_INET, SOCK_DGRAM) + else: + raise 'Unsupported protocol {}'.format(self.proto) + + # it's important to extend timeout for upgrade procedure + self.timeout = timeout + self.socket.settimeout(timeout) + + def close(self): + self.alive.cancel() + self.socket.close() + self.socket = None + + def udp_socket_send(self, bytes): + return self.socket.sendto(bytes, (self.ip, self.port)) + + def udp_socket_recv(self, bytes): + data, _ = self.socket.recvfrom(bytes) + return data + + def tcp_socket_send(self, bytes): + return self.socket.sendall(bytes) + + def tcp_socket_recv(self, bufsize): + return self.socket.recv(bufsize) + + def receive_with_timeout(self, length): + received = 0 + buf = bytearray() + start_time = time.time() + + while True: + data = self.socket_recv(length - received) + buf.extend(data) + received += len(data) + if length == received: + break + elapsed_time = time.time() - start_time + if elapsed_time > self.timeout: + return None + return buf + + def receive_json(self, length): + data = self.receive_with_timeout(length).decode('utf-8') + if data is None: + return {} + + self.packet_count += 1 + self.logger.debug("<= %s", data) + reply = json.loads(data[:-2]) + return reply + + def send(self, msg, data={}, wait_response=True): + if self.socket is None: + return {"Ret": 101} + # self.busy.wait() + self.busy.acquire() + if hasattr(data, "__iter__"): + if sys.version_info[0] > 2: + data = bytes(json.dumps(data, ensure_ascii=False), "utf-8") + else: + data = json.dumps(data, ensure_ascii=False) + pkt = ( + struct.pack( + "BB2xII2xHI", + 255, + 0, + self.session, + self.packet_count, + msg, + len(data) + 2, + ) + + data + + b"\x0a\x00" + ) + self.logger.debug("=> %s", pkt) + self.socket_send(pkt) + if wait_response: + reply = {"Ret": 101} + ( + head, + version, + self.session, + sequence_number, + msgid, + len_data, + ) = struct.unpack("BB2xII2xHI", self.socket_recv(20)) + reply = self.receive_json(len_data) + self.busy.release() + return reply + + def sofia_hash(self, password=""): + if sys.version_info[0] > 2: + md5 = hashlib.md5(bytes(password, "utf-8")).digest() + else: + md5 = hashlib.md5(password.decode('utf-8')).digest() + + chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + return "".join([chars[sum(x) % 62] for x in zip(md5[::2], md5[1::2])]) + + def login(self): + if self.socket is None: + self.connect() + data = self.send( + 1000, + { + "EncryptType": "MD5", + "LoginType": "DVRIP-Web", + "PassWord": self.hash_pass, + "UserName": self.user, + }, + ) + if data["Ret"] not in self.OK_CODES: + return False + self.session = int(data["SessionID"], 16) + self.alive_time = data["AliveInterval"] + self.keep_alive() + return data["Ret"] in self.OK_CODES + + def keep_alive(self): + self.send( + 1006, + {"Name": "KeepAlive", "SessionID": "0x%08X" % self.session}, + ) + self.alive = threading.Timer(self.alive_time, self.keep_alive) + self.alive.start() + + def get_info(self, command): + return self.get_command(command, 1042) + + def get_command(self, command, code): + data = self.send(code, {"Name": command, "SessionID": "0x%08X" % self.session}) + if data["Ret"] in self.OK_CODES and command in data: + return data[command] + else: + return data + + def set_info(self, command, data): + return self.set_command(command, data, 1040) + + def set_command(self, command, data, code): + return self.send( + code, {"Name": command, "SessionID": "0x%08X" % self.session, command: data} + ) + + +if __name__ == '__main__': + if len(sys.argv) < 3: + print("This script allows you to set your Camera's IP address.") + print('') + print('To use the script, your camera must be attached to your home network') + print('and acessible via its default IP address. If you are not sure what') + print('what that is, you may be able to find out from your home router. Most routers') + print("have a page that displys connected clients. Look for one named 'HostName',") + print('and make a note of its address. Alternatively you can use free tools like ') + print('Advanced IP-Scanner to scan your network for the same name. ') + print('') + print('You will also need to know the address you want your camera to have.') + print('Normally this will be 192.168.42.10') + print('') + print('Once you have this information and the camera is on your network') + print('call this module again with two arguments, the CURRENT and DESIRED address, eg:') + print('') + print(' python -m Utils.SetCameraAddress 192.168.1.100 192.168.42.10') + print('') + print('replacing the two addresses as needed') + print('') + exit(0) + + ipaddr = sys.argv[1] + newaddr = sys.argv[2] + cam=DVRIPCam(ipaddr) + if cam.login(): + + nc=cam.get_info("NetWork.NetCommon.HostIP") + dh=cam.get_info("NetWork.NetDHCP.[0].Enable") + print('current address {}, dhcp enabled is {}'.format(iptoString(nc), dh)) + + print('--------') + print('setting address to {}'.format(newaddr)) + cam.set_info("NetWork.NetDHCP.[0].Enable", 0) + hexval = strIPtoHex(newaddr) + cam.set_info("NetWork.NetCommon.HostIP", hexval) + + print('--------') + cam.close() + else: + print('login failed') From 761c0efb721c89eaa7b2d43c24df934b92106e17 Mon Sep 17 00:00:00 2001 From: Mark McIntyre Date: Thu, 29 Apr 2021 23:02:17 +0100 Subject: [PATCH 2/3] defence against bad IPaddresses --- Utils/SetCameraAddress.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Utils/SetCameraAddress.py b/Utils/SetCameraAddress.py index 7c6007f01..cf5cf5809 100644 --- a/Utils/SetCameraAddress.py +++ b/Utils/SetCameraAddress.py @@ -11,6 +11,20 @@ import socket as mysocket +def checkValidIPAddr(addr): + spls = addr.split('.') + if len(spls) != 4: + return False + for s in spls: + try: + intv = int(s) + except: + return False + if intv < 1 or intv > 254: + return False + return True + + def strIPtoHex(ip): a = binascii.hexlify(mysocket.inet_aton(ip)).decode().upper() addr='0x'+''.join([a[x:x+2] for x in range(0,len(a),2)][::-1]) @@ -229,6 +243,15 @@ def set_command(self, command, data, code): ipaddr = sys.argv[1] newaddr = sys.argv[2] + + if not checkValidIPAddr(ipaddr) or not checkValidIPAddr(newaddr): + print('') + print('One or both IP Addresses seems invalid - check that they are correct and in ') + print('dotted form ie a.b.c.d where a,b,c and d are numbers between 1 and 254') + print('') + print('') + exit(0) + cam=DVRIPCam(ipaddr) if cam.login(): From d23092fc572c736f5b0bae418b12ce2a43e7506d Mon Sep 17 00:00:00 2001 From: Mark McIntyre Date: Sat, 1 May 2021 18:54:30 +0100 Subject: [PATCH 3/3] Improvements to cleanly end --- Utils/SetCameraAddress.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/Utils/SetCameraAddress.py b/Utils/SetCameraAddress.py index cf5cf5809..85c0bb0cb 100644 --- a/Utils/SetCameraAddress.py +++ b/Utils/SetCameraAddress.py @@ -63,7 +63,7 @@ def __init__(self, ip, **kwargs): self.alarm_func = None self.busy = threading.Condition() - def connect(self, timeout=10): + def connect(self, timeout=2): if self.proto == "tcp": self.socket_send = self.tcp_socket_send self.socket_recv = self.tcp_socket_recv @@ -76,7 +76,6 @@ def connect(self, timeout=10): else: raise 'Unsupported protocol {}'.format(self.proto) - # it's important to extend timeout for upgrade procedure self.timeout = timeout self.socket.settimeout(timeout) @@ -255,17 +254,32 @@ def set_command(self, command, data, code): cam=DVRIPCam(ipaddr) if cam.login(): + print('--------') + print('The process will appear to hang for several seconds then should print the ') + print('new address. ') + print('--------') nc=cam.get_info("NetWork.NetCommon.HostIP") dh=cam.get_info("NetWork.NetDHCP.[0].Enable") print('current address {}, dhcp enabled is {}'.format(iptoString(nc), dh)) print('--------') print('setting address to {}'.format(newaddr)) + print('--------') cam.set_info("NetWork.NetDHCP.[0].Enable", 0) hexval = strIPtoHex(newaddr) - cam.set_info("NetWork.NetCommon.HostIP", hexval) - - print('--------') - cam.close() + try: + # this wil actually succeed, but a timeout will occur once + # the camera address is changed. + cam.set_info("NetWork.NetCommon.HostIP", hexval) + except mysocket.timeout: + cam2=DVRIPCam(newaddr) + cam2.login() + nc=cam2.get_info("NetWork.NetCommon.HostIP") + dh=cam2.get_info("NetWork.NetDHCP.[0].Enable") + print('Address now {}, dhcp enabled is {}'.format(iptoString(nc), dh)) + cam2.close() + cam.close() + print('--------') + exit(0) else: print('login failed')