diff --git a/README.md b/README.md index 21d5ea5..ba319c4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ # cam -A simple script to control Xiongmai and related cameras using the anyka_ipc command daemon and "NETSDK" interface. It might work on others as well, it might not work at all. It works for me, mostly. \ No newline at end of file +Cam controls cameras, or is supposed to. It is supposed to be more useable than the abominable web interface these things tend to come with. It is a simple script to control Xiongmai and related cameras using the `anyka_ipc` command daemon and `NETSDK` interface. It might work on others as well, it might not work at all. It works for me, mostly. + +Use: `cam [parameters]` + +# configuration + +You will need to edit the script to configure your camera IP addresses. If you changed the username and password on the device you can enter these as well but it is by no means guaranteed to work since it is quite probably that username `admin` and a blank password is hardcoded somewhere in the firmware on your camera. + +Run the script without any parameters to see the available commands. Some commands take parameters, use `can 1 [command] -H` to see the available parameters and the accepted value ranges. + +This script is based on what I could discern from a firmware dump parts of which I ran through Ghidra. diff --git a/cam b/cam new file mode 100755 index 0000000..3838afa --- /dev/null +++ b/cam @@ -0,0 +1,456 @@ +#!/bin/bash + +shopt -s extglob + +declare -A camera=( + [1]=192.168.5.10 + [2]=192.168.5.11 + [3]=192.168.5.12 + [4]=192.168.5.13 + [5]=192.168.5.14 + [6]=192.168.5.15 + [7]=192.168.5.16 + [8]=192.168.5.17 +) + +camlst=$(echo -n "${!camera[@]}"|tr ' ' '|') + +username="admin" +password="" +auth=$(echo -n "$username:$password"|base64) + +viewer="display" +player="mpv" + +headers="-H 'Authorization: Basic $auth'" +# headers="-H 'Authorization: Basic $auth' -H 'Cookie: juanipcam_lang=en; login=${username}%2C${password}; sync_time=true; usr=$username; pwd=$password'" + +declare -A SDK=( + [audio_encode]=/NetSDK/Audio/encode/channel/101 + [audio_input]=/NetSDK/Audio/input/channel/1 + [image]=/NetSDK/Image + [image_ircut]=/NetSDK/Image/irCutFilter + [image_denoise3d]=/NetSDK/Image/denoise3d + [image_sharpness]=/NetSDK/Image/manualSharpness + [image_videomode]=/NetSDK/Image/videoMode/properties/ + [image_wdr]=/NetSDK/Image/wdr + [network_dns]=/NetSDK/Network/DNS + [network_esee]=/NetSDK/Network/ESee + [network_lan1]=/NetSDK/Network/interface/1/lan + [network_lan4]=/NetSDK/Network/interface/1/lan + [network_wifi]=/NetSDK/Network/interface/4/wireless + [network_port]=/NetSDK/Network/port + [wifi_allstainfo]=/NetSDK/Network/wireless/allStaInfo + [wifi_stationsignal]=/NetSDK/Network/wireless/stationSignal + [wifi_status]=/NetSDK/Network/Wireless/status + [ptz]=/NetSDK/PTZ/channel/1/control + [devinfo]=/NetSDK/System/deviceInfo + [reboot]=/NetSDK/System/operation/reboot + [time]=/NetSDK/System/time/localtime + [time_ntp]=/NetSDK/System/time/ntp + [time_rtc]=/NetSDK/System/time/rtc + [time_zone]=/NetSDK/System/time/timeZone + [time_calender]=/NetSDK/System/time/calendarStyle + [vencode_main]=/NetSDK/Video/encode/channel/101 + [vencode_sub]=/NetSDK/Video/encode/channel/102 + [video_channeloverlay]=/NetSDK/Video/encode/channel/101/channelNameOverlay + [video_extraoverlay]=/NetSDK/Video/encode/channel/101 + [video_timeoverlay]=/NetSDK/Video/encode/channel/101/datetimeOverlay + [video_input]=/NetSDK/Video/input/channel/1 + [video_brightness]=/NetSDK/Video/input/channel/1/brightnessLevel + [video_contrast]=/NetSDK/Video/input/channel/1/contrastLevel + [video_flip]=/NetSDK/Video/input/channel/1/flipEnabled + [video_mirror]=/NetSDK/Video/input/channel/1/mirrorEnabled + [video_hue]=/NetSDK/Video/input/channel/1/hueLevel + [video_mask]=/NetSDK/Video/input/channel/1/PrivacyMasks + [video_saturation]=/NetSDK/Video/input/channel/1/saturationLevel + [video_sharpness]=/NetSDK/Video/input/channel/1/sharpnessLevel + [motiondetection]=/NetSDK/Video/motionDetection/channel/1 + [load]="/_SDK/info/monitor.php?nonce=123" + [ps]="/_SDK/info/monitor.php?nonce=123" + [debuginfo]="/_SDK/info/device.php?nonce=123" + [eventinfo]="/_SDK/info/event.php?nonce=123" + [health]=/Anyka/health + [config]=/Anyka/config + [log]=/Anyka/log + [oem]=/custom/OEM + [view_main]="/ch0_0.264" + [view_sub]="/ch0_1.264" + [snapshot]=/snapshot + [user_list]="/user/user_list.xml?username=$username&password=$password" +) + +declare -A audio_encode=(e enabled:b i audioInputChannelID:i c codecType:s) +declare -A audio_input=(w workMode:s m microphoneType:s:auto,activePickup,passiveMic r sampleRate:i w sampleBitWidth:i i inputVolume:i o outputVolume:i) +declare -A image_ircut=(c irCutControlMode:s:hardware,software m irCutMode:s:auto,daylight,night) +declare -A image_denoise3d=(e enabled:b s denoise3dStrength:i) +declare -A image_sharpness=(e enabled:b -s sharpnessLevel:i) +declare -A image_wdr=(e enabled:b -s WDRStrength:i) +declare -A image=(s imageStyle:i:1,2,3 l lowlightMode:s:close,day-night,only-night,auto c sceneMode:s:auto,indoor,outdoor e exposureMode:s:auto,bright,dark a awbMode:s:auto,indoor,outdoor b BLcompensationMode:s) +declare -A image_videomode=(s imageStyle:i:1,2,3 l lowlightMode:s:close,day-night,only-night,auto c sceneMode:s:auto,indoor,outdoor e exposureMode:s:auto,bright,dark a awbMode:s:auto,indoor,outdoor b BLcompensationMode:s) +declare -A network_dns=(p preferredDns:s a staticAlternateDns:s) +declare -A network_esee=(e enabled:b) +declare -A network_lan1=(v ipVersion:s i staticIP:s n staticNetmask:s g staticGateway:s t addressingType:s o OnvifAutoAdapt:b d dhcp:b) +declare -A network_lan4=(v ipVersion:s i staticIP:s n staticNetmask:s g staticGateway:s t addressingType:s o OnvifAutoAdapt:b d dhcp:b) +declare -A network_wifi=(m wirelessMode:s s stationMode.wirelessStaMode:s b stationMode.wirelessApBssId:s a stationMode.wirelessApEssId:s p stationMode.wirelessApPsk:s) +declare -A network_port=(n portname:s p value:i) +declare -A reboot=() +declare -A time_ntp=(e enabled:b s ntpServerDomain:s) +declare -A time_zone=() +declare -A time=() +declare -A vencode_main=(e enabled:b r resolution:s c codecType:s b constantBitrate:i f frameRate:i t bitRateControlType:s p h264Profile:s k keyFrameInterval:i n channelName:s F freeResolution:b ) +declare -A vencode_sub=(e enabled:b r resolution:s c codecType:s b constantBitrate:i f frameRate:i t bitRateControlType:s p h264Profile:s k keyFrameInterval:i n channelName:s F freeResolution:b ) +declare -A video_timeoverlay=(e enabled:b x regionX:i y regionY:i d dateFormat:s t timeFormat:i w displayWeek:b c displayChinese:b) +declare -A video_channeloverlay=(e enabled:b x regionX:i y regionY:i) +declare -A video_extraoverlay=(e expandChannelNameOverlay[0].enabled:b t expandChannelNameOverlay[0].expandChannelName:s) +declare -A video_input=(e enabled:b l powerLineFrequencyMode:i:50,60,100,120 b brightnessLevel:i:0-100 c contrastLevel:i:0-100 s sharpnessLevel:i:0-100 a saturationLevel:i:0-100 h hueLevel:i:0-100 f flipEnabled:b m mirrorEnabled:b p privacyMask) +declare -A video_flip=() +declare -A video_mirror=() +declare -A video_brightness=() +declare -A video_contrast=() +declare -A video_saturation=() +declare -A video_hue=() +declare -A video_sharpness=() + +is_json () { + jq -e . >/dev/null 2>&1 <<<"$@" +} + +cam_url () { + cam="$1" + echo -n "http://${username}:${password}@${camera[$cam]}" +} + +get () { + cam="$1" + cmd="$2" + curl -s "$headers" "$(cam_url $cam)${SDK[$cmd]}" +} + +put () { + cam="$1" + cmd="$2" + data="$3" + curl -g -s "$headers" "$(cam_url $cam)${SDK[$cmd]}" -X PUT --data-raw "$(echo "$data")" +} + +get_json () { + cam="$1" + command="$2" + jqq="$3" + if [ -z "$jqq" ]; then + jqq="." + fi + echo -n "$command CAM$cam (${camera[$cam]}): " + result=$(get "$cam" "$command") + if is_json "$result"; then + jq "$jqq"<<<"$result" + else + echo "$result" + fi +} + +put_json () { + cam="$1" + command="$2" + data="$3" + jqq="$4" + if [ -z "$jqq" ]; then + jqq=".statusMessage" + fi + echo -n "$command CAM$cam (${camera[$cam]}): " + put "${cam}" "$command" "$data"|jq "$jqq" +} + +get_text () { + cam="$1" + echo -n "$command CAM$cam (${camera[$cam]}):" + get "$cam" "$command"|tr -cd "[:print:]\n\t" + echo +} + +check_range () { + value="$1" + range=$(echo "$2"|tr ',' '|') + field="$3" + if [[ "$range" == "$2" ]]; then + min=$(echo "$2"|cut -d '-' -f 1) + max=$(echo "$2"|cut -d '-' -f 2) + if [[ "$min" == "$max" ]]; then + printf "$field: invalid range specification\n\n" + exit 1 + else + if [[ $value -ge $min && $value -le $max ]]; then + return + fi + fi + else + if [[ "$value" == @($range) ]]; then + return + fi + fi + + printf "$field: value '$value' invalid\n\n" + printf "allowed values: $2\n" + exit 1 +} + +mutate () { + data="$1" + fieldtype="$2" + value="$3" + field=$(echo "$fieldtype"|cut -d ':' -f 1) + type=$(echo "$fieldtype"|cut -d ':' -f 2) + range=$(echo "$fieldtype"|cut -d ':' -f 3) + if [[ -n $range ]]; then + check_range "$value" "$range" "$field" + fi + case $type in + b|i) + jq ".$field=$value" <<<"$data" + ;; + s) + jq ".$field=\"$value\"" <<<"$data" + ;; + *) + jq ".$field=\"$value\"" <<<"$data" + ;; + esac +} + +help_command () { + command="$1" + declare -n opts="$command" + if [[ ${#opts[@]} -lt 1 ]]; then + printf "The $command command takes a single parameter and has no options\n" + else + printf "Available parameters for $command:\n\n" + for n in ${!opts[@]}; do printf "\t-$n\t$(echo "${opts[$n]}"| sed 's/:/ (type /;s/:/ values:/;s/$/\)/')\n";done + fi + echo + exit +} + + +config () { + cam="$1" + shift + command="$1" + shift + cam_ip="${camera[$cam]}" + declare -n opts="$command" + optstr="$(tr ' ' ':'<<<"${!opts[@]}"):H" + optlst="$(echo "${!opts[@]}"|sed -e 's/ /|/g')" + cc=$(get "$cam" "$command") + local OPTIND + while getopts "$optstr" OPTION; do + optset=1 + case $OPTION in + H) + help_command $command + ;; + @($optlst)) + cc="$(mutate "$cc" "${opts[$OPTION]}" "$OPTARG")" || exit_with_error "$cc" + ;; + *) + echo "unknown option $OPTION" + exit + ;; + esac + done + if [ -v optset ];then + put_json "$cam" "$command" "$cc" + elif (declare -p $opts 2>&1>/dev/null) && [[ -z $optlst ]] && [[ -n $1 ]]; then + put_json "$cam" "$command" "\"$1\"" + else + get_json "$cam" "$command" + fi +} + +view () { + cam="$1" + cmd="$2" + $viewer "$(cam_url $cam)${SDK[$cmd]}" & +} + +watch () { + cam="$1" + cmd="$2" + $player "rtsp://$username:$password@${camera[$cam]}${SDK[$cmd]}" & +} + +help () { + printf "Use: $(basename $0) command [camera|'all']\n\n" + printf "available commands:\n\n" + printf '%s\n' "${!SDK[@]}"|sort + exit +} + +exit_with_error () { + printf "ERROR: $@\n" + exit 1 +} + +# --------------------------------------------------- + +case $1 in + @($camlst)) + cam_s=$1 + cam_f=$1 + shift + ;; + all) + cam_s=1 + cam_f=8 + shift + ;; + *) + printf "camera number or 'all' missing\n\n" + help +esac + +if [[ -z $1 ]]; then + help +else + command="$1" + shift +fi + +if ! test ${SDK[$command]+_}; then + printf "unknown command $command\n\n" + help +fi + +for cam in $(seq "$cam_s" "$cam_f");do + case "$command" in + load) + get_json "$cam" "$command" ".CPU.Usage" + ;; + health|config|log) + get_text "$cam" + ;; + reboot) + put_json "$cam" "$command" '{true}' + ;; + snapshot) + view "$cam" "$command" + ;; + view_main|view_sub) + watch "$cam" "$command" + ;; + help) + help + ;; + *) + config "$cam" "$command" "$@" + ;; + esac +done + +exit + +# based on strings pulled from firmware dump, these represent api endpoints +# used with GET or PUT requests as defined in NK_N1Device_InitV2() +# +# check http://camera_ip/js/function.js (etc) for how these endpoints seem to expect +# to be fed amd cared for - "seem" because the firmware is quite buggy and not all +# endpoints seem to be connected. +# +# /NetSDK/ +# /NetSDK/Audio/encode/channel +# /NetSDK/Audio/encode/channel/[id=101] +# /NetSDK/Audio/encode/channel/[id=101]/enabled +# /NetSDK/Audio/input/channel +# /NetSDK/Audio/input/channel/[id=1] +# /NetSDK/Audio/input/channel/[id=1]/inputVolume +# /NetSDK/Audio/input/channel/[id=1]/microphoneType +# /NetSDK/Audio/input/channel/[id=1]/outputVolume +# /NetSDK/Audio/input/channel/[id=1]/sampleBitWidth +# /NetSDK/Audio/input/channel/[id=1]/sampleRate +# /NetSDK/Factory +# /NetSDK/Image +# /NetSDK/Image/denoise3d +# /NetSDK/Image/irCutFilter +# /NetSDK/Image/manualSharpness +# /netsdk/image/videoMode +# /NetSDK/Image/videoMode +# /NetSDK/Image/wdr +# /NetSDK/Network/DNS +# /NetSDK/Network/ESee +# /NetSDK/Network/interface +# /NetSDK/Network/interface/[id=1] +# /NetSDK/Network/interface/[id=1]/lan +# /NetSDK/Network/interface/[id=4] +# /NetSDK/Network/interface/[id=4]/lan +# /NetSDK/Network/interface/[id=4]/wireless +# /NetSDK/Network/port +# /NetSDK/Network/port/[id=1] +# /NetSDK/Network/wireless/allStaInfo +# /NetSDK/Network/wireless/stationSignal +# /NetSDK/Network/Wireless/status +# /NetSDK/PTZ/channel/[id=1]/control +# /NetSDK/snapshot +# /NetSDK/snapshot.jpg +# /NetSDK/System/deviceInfo +# /NetSDK/System/deviceInfo/deviceAddress +# /NetSDK/System/deviceInfo/deviceName +# /NetSDK/System/operation +# /NetSDK/System/operation/default +# /NetSDK/System/operation/reboot +# /NetSDK/System/operation/remoteUpgrade +# /NetSDK/System/time +# /NetSDK/System/time/calendarStyle +# /NetSDK/System/time/localTime +# /NetSDK/System/time/ntp +# /NetSDK/System/time/rtc +# /NetSDK/System/time/timeZone +# /NetSDK/Video/encode/channel +# /NetSDK/Video/encode/channel/101/snapshot +# /NetSDK/Video/encode/channel/102/snapshot +# /NetSDK/Video/encode/channel/[id=101] +# /NetSDK/Video/encode/channel/[id=101]/channelNameOverlay +# /NetSDK/Video/encode/channel/[id=101]/datetimeOverlay +# /NetSDK/Video/encode/channel/[id=101]/requestKeyFrame +# /NetSDK/Video/encode/channel/[id=102] +# /NetSDK/Video/encode/channel/[id=102]/requestKeyFrame +# /NetSDK/Video/encode/channel/[id=1]/requestKeyFrame +# /NetSDK/Video/encode/channel/[id=2]/requestKeyFrame +# /NetSDK/Video/input/channel +# /NetSDK/Video/input/channel/[id=1] +# /NetSDK/Video/input/channel/[id=1]/brightnessLevel +# /NetSDK/Video/input/channel/[id=1]/contrastLevel +# /NetSDK/Video/input/channel/[id=1]/flipEnabled +# /NetSDK/Video/input/channel/[id=1]/hueLevel +# /NetSDK/Video/input/channel/[id=1]/mirrorEnabled +# /NetSDK/Video/input/channel/[id=1]/PrivacyMask +# /NetSDK/Video/input/channel/[id=1]/saturationLevel +# /NetSDK/Video/input/channel/[id=1]/sharpnessLevel +# /NetSDK/Video/motionDetection/channel +# /NetSDK/Video/motionDetection/channel/[id=1] +# +# /cgi-bin/gw2.cgi +# /cgi-bin/hi3510/echo.cgi +# /cgi-bin/hi3510/ptzctrl.cgi +# /cgi-bin/hi3510/preset.cgi +# /cgi-bin/hi3510/param.cgi +# +# /livestream/11 +# /livestream/12 +# +# /livestream/hinetate/11 +# /livestream/hinetate/12 +# +# /snapshot +# /snapshot.jpg +# +# /user/add_user.xml +# /user/del_user.xml +# /user/set_pass.xml +# /user/user_list.xml +# +# /_SDK/index.php +# /_SDK/info/device.php +# /_SDK/info/event.php +# /_SDK/info/license.php +# /_SDK/info/monitor.php +#