diff --git a/contrib/DS_Starter/49_SSCam.pm b/contrib/DS_Starter/49_SSCam.pm index ac629b707..a4737ab01 100644 --- a/contrib/DS_Starter/49_SSCam.pm +++ b/contrib/DS_Starter/49_SSCam.pm @@ -1,9 +1,9 @@ ######################################################################################################################## -# $Id: 49_SSCam.pm 22600 2020-08-14 19:22:36Z DS_Starter $ +# $Id: 49_SSCam.pm 23781 2021-02-20 14:22:03Z DS_Starter $ ######################################################################################################################### # 49_SSCam.pm # -# (c) 2015-2020 by Heiko Maaz +# (c) 2015-2021 by Heiko Maaz # e-mail: Heiko dot Maaz at t-online dot de # # This Module can be used to operate Cameras defined in Synology Surveillance Station 7.0 or higher. @@ -36,14 +36,39 @@ package FHEM::SSCam; use strict; use warnings; -use GPUtils qw(GP_Import GP_Export); # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt +use GPUtils qw( GP_Import GP_Export ); # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt + +use FHEM::SynoModules::API qw(:all); # API Modul +use FHEM::SynoModules::ErrCodes qw(:all); # Error Code Modul +use FHEM::SynoModules::SMUtils qw( + getClHash + delClHash + trim + moduleVersion + sortVersion + showModuleInfo + jboolmap + completeAPI + showAPIinfo + setCredentials + getCredentials + showStoredCredentials + evaljson + login + logout + setActiveToken + delActiveToken + delCallParts + setReadingErrorNone + setReadingErrorState + ); # Hilfsroutinen Modul use Data::Dumper; use MIME::Base64; use Time::HiRes qw( gettimeofday tv_interval ); use HttpUtils; use Blocking; # für EMail-Versand use Encode; -eval "use JSON;1;" or my $SScamMMDBI = "JSON"; ## no critic 'eval' # Debian: apt-get install libjson-perl +eval "use JSON;1;" or my $MMJSON = "JSON"; ## no critic 'eval' # Debian: apt-get install libjson-perl eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; ## no critic 'eval' # Cache @@ -137,7 +162,7 @@ BEGIN { TelegramBot_Callback TelegramBot_BinaryFileRead FHEM::SSChatBot::formString - FHEM::SSChatBot::addQueue + FHEM::SSChatBot::addSendqueue FHEM::SSChatBot::getApiSites ) ); @@ -159,6 +184,43 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "9.8.5" => "22.02.2021 remove sscam_tooltip.js ", + "9.8.4" => "20.02.2021 sub Define minor fix ", + "9.8.3" => "29.11.2020 fix cannot send snaps/recs if snapTelegramTxt + snapChatTxt and no cacheType (cacheType=internal) is set ", + "9.8.2" => "04.10.2020 use showStoredCredentials from SMUtils ", + "9.8.1" => "28.09.2020 align getApiSites_Parse to other syno modules ", + "9.8.0" => "27.09.2020 optimize getApiSites_Parse, new getter apiInfo ", + "9.7.26" => "26.09.2020 use moduleVersion and other from SMUtils ", + "9.7.25" => "25.09.2020 change FHEM::SSChatBot::addQueue to FHEM::SSChatBot::addSendqueue ", + "9.7.24" => "24.09.2020 optimize prepareSendData ", + "9.7.23" => "23.09.2020 setVersionInfo back from SMUtils, separate prepareSendData ", + "9.7.22" => "22.09.2020 bugfix error condition if try new login in some cases ", + "9.7.21" => "21.09.2020 control parse function by the hparse hash step 4 ", + "9.7.20" => "20.09.2020 control parse function by the hparse hash step 3 (refactored getsnapinfo, getsnapgallery, runView Snap) ", + "9.7.19" => "18.09.2020 control parse function by the hparse hash step 2 ", + "9.7.18" => "16.09.2020 control parse function by the hparse hash ", + "9.7.17" => "13.09.2020 optimize _Oprunliveview ", + "9.7.16" => "12.09.2020 function _Oprunliveview to execute livestream (no snap / HLS) in new camOp variant ", + "9.7.15" => "12.09.2020 changed audiolink handling to new execution variant in camOp ", + "9.7.14" => "10.09.2020 bugfix in reactivation HLS streaming ", + "9.7.13" => "10.09.2020 optimize liveview handling ", + "9.7.12" => "09.09.2020 implement new getApiSites usage, httptimeout default value increased to 20s, fix setting motdetsc ", + "9.7.11" => "07.09.2020 implement new camOp control ", + "9.7.10" => "06.09.2020 rebuild timer sequences, minor fixes ", + "9.7.9" => "05.09.2020 more refactoring according PBP ", + "9.7.8" => "02.09.2020 refactored setter: pirSensor runPatrol goAbsPTZ move runView hlsreactivate hlsactivate refresh ". + "extevent stopView setPreset setHome, camOP_parse for extevent, use setReadingErrorNone from SMUtils ". + "fix setting CamNTPServer", + "9.7.7" => "01.09.2020 minor fixes, refactored setter: createReadingsGroup enable disable motdetsc expmode homeMode ". + "autocreateCams goPreset optimizeParams ", + "9.7.6" => "31.08.2020 refactored setter: snapGallery createSnapGallery createPTZcontrol createStreamDev, minor bugfixes ", + "9.7.5" => "30.08.2020 some more code review and optimisation, exitOnDis with fix check Availability instead of state ", + "9.7.4" => "29.08.2020 some code changes ", + "9.7.3" => "29.08.2020 move login, loginReturn, logout, logoutReturn, setActiveToken, delActiveToken to SMUtils.pm ". + "move expErrorsAuth, expErrors to ErrCodes.pm", + "9.7.2" => "26.08.2020 move setCredentials, getCredentials, evaljson to SMUtils.pm ", + "9.7.1" => "25.08.2020 switch to lib/FHEM/SynoModules/API.pm and lib/FHEM/SynoModules/SMUtils.pm ". + "move __getPtzPresetList, __getPtzPatrolList to return path of OpMOde Getcaminfo ", "9.7.0" => "17.08.2020 compatibility to SSChatBot version 1.10.0 ", "9.6.1" => "13.08.2020 avoid warnings during FHEM shutdown/restart ", "9.6.0" => "12.08.2020 new attribute ptzNoCapPrePat ", @@ -181,7 +243,7 @@ my %vNotesIntern = ( "9.0.6" => "26.11.2019 minor code change ", "9.0.5" => "22.11.2019 commandref revised ", "9.0.4" => "18.11.2019 fix FHEM crash when sending data by telegramBot, Forum: https://forum.fhem.de/index.php/topic,105486.0.html ", - "9.0.3" => "04.11.2019 change send Telegram routines, undef variables, fix cache and transaction coding, fix sendEmailblocking ", + "9.0.3" => "04.11.2019 change send Telegram routines, undef variables, fix cache and transaction coding, fix __sendEmailblocking ", "9.0.2" => "03.11.2019 change Streamdev type \"lastsnap\" use \$data Hash or CHI cache ", "9.0.1" => "02.11.2019 correct snapgallery number of snaps in case of cache usage, fix display number of retrieved snaps ", "9.0.0" => "26.10.2019 finalize all changes beginning with 8.20.0 and revised commandref ", @@ -190,10 +252,10 @@ my %vNotesIntern = ( "8.21.0" => "20.10.2019 implement Redis driver for CHI ", "8.20.0" => "19.10.2019 implement caching with CHI, implement {SENDCOUNT} ", "8.19.6" => "14.10.2019 optimize memory usage of composeGallery ", - "8.19.5" => "13.10.2019 change FH to Data in sendEmailblocking, save variables ", + "8.19.5" => "13.10.2019 change FH to Data in __sendEmailblocking, save variables ", "8.19.4" => "11.10.2019 further optimize memory usage when send recordings by email and/or telegram ", "8.19.3" => "09.10.2019 optimize memory usage when send images and recordings by email and/or telegram ", - "8.19.2" => "06.10.2019 delete key/value pairs in extractForTelegram and sendEmailblocking, ". + "8.19.2" => "06.10.2019 delete key/value pairs in __extractForTelegram and sendEmailblocking, ". "change datacontainer of SNAPHASH(OLD) from %defs to %data ", "8.19.1" => "26.09.2019 set compatibility to 8.2.6 ", "8.19.0" => "21.09.2019 support attr \"hideAudio\" SSCamSTRM-device ", @@ -214,6 +276,7 @@ my %vNotesIntern = ( # Versions History extern my %vNotesExtern = ( + "9.8.0" => "27.09.2020 New get command 'apiInfo' retrieves the API information and opens a popup window to show it. ", "9.6.0" => "12.08.2020 The new attribute 'ptzNoCapPrePat' is available. It's helpful if your PTZ camera doesn't have the capability ". "to deliver Presets and Patrols. Setting the attribute avoid error log messages in that case. ", "9.5.0" => "15.07.2020 A new type 'master' supplements the possible createStreamDev command options. The streaming type ". @@ -298,7 +361,7 @@ my %vNotesExtern = ( "3.4.0" => "21.03.2018 new commands startTracking, stopTracking ", "3.3.1" => "20.03.2018 new readings CapPTZObjTracking, CapPTZPresetNumber ", "3.3.0" => "25.02.2018 code review, API bug fix of runview lastrec, commandref revised (forum:#84953) ", - "3.2.4" => "18.11.2017 fix bug don't retrieve getPtzPresetList if cam is disabled ", + "3.2.4" => "18.11.2017 fix bug don't retrieve __getPtzPresetList if cam is disabled ", "3.2.3" => "08.10.2017 set optimizeParams, get caminfo (simple), minor bugfix, commandref revised ", "3.2.0" => "27.09.2017 new command get listLog, change to \$hash->{HELPER}{\".SNAPHASH\"} for avoid huge \"list\"-report ", "3.1.0" => "26.09.2017 move extevent from CAM to SVS model, Reading PollState enhanced for CAM-Model, minor fixes ", @@ -347,55 +410,6 @@ my %vNotesExtern = ( "1.0.0" => "12.12.2015 initial, changed completly to HttpUtils_NonblockingGet " ); -# Aufbau Errorcode-Hashes (siehe Surveillance Station Web API) -my %errauthlist = ( - 100 => "Unknown error", - 101 => "The account parameter is not specified", - 102 => "API does not exist", - 400 => "Invalid user or password", - 401 => "Guest or disabled account", - 402 => "Permission denied - DSM-Session: make sure user is member of Admin-group, SVS-Session: make sure SVS package is started, make sure FHEM-Server IP won't be blocked in DSM automated blocking list", - 403 => "One time password not specified", - 404 => "One time password authenticate failed", - 405 => "method not allowd - maybe the password is too long", - 406 => "OTP code enforced", - 407 => "Max Tries (if auto blocking is set to true) - make sure FHEM-Server IP won't be blocked in DSM automated blocking list", - 408 => "Password Expired Can not Change", - 409 => "Password Expired", - 410 => "Password must change (when first time use or after reset password by admin)", - 411 => "Account Locked (when account max try exceed)", -); - -my %errlist = ( - 100 => "Unknown error", - 101 => "Invalid parameters", - 102 => "API does not exist", - 103 => "Method does not exist", - 104 => "This API version is not supported", - 105 => "Insufficient user privilege", - 106 => "Connection time out", - 107 => "Multiple login detected", - 117 => "need manager rights in SurveillanceStation for operation", - 400 => "Execution failed", - 401 => "Parameter invalid", - 402 => "Camera disabled", - 403 => "Insufficient license", - 404 => "Codec activation failed", - 405 => "CMS server connection failed", - 407 => "CMS closed", - 410 => "Service is not enabled", - 412 => "Need to add license", - 413 => "Reach the maximum of platform", - 414 => "Some events not exist", - 415 => "message connect failed", - 417 => "Test Connection Error", - 418 => "Object is not exist", - 419 => "Visualstation name repetition", - 439 => "Too many items selected", - 502 => "Camera disconnected", - 600 => "Presetname and PresetID not found in Hash", -); - # Tooltipps Textbausteine (http://www.walterzorn.de/tooltip/tooltip.htm#download), §NAME§ wird durch Kameranamen ersetzt my %ttips_en = ( ttrefresh => "The playback of streaming content of camera of "§NAME§" will be restartet.", @@ -433,11 +447,159 @@ my %ttips_de = ( helpsvs => "Die Onlinehilfe der Synology Surveillance Station wird in einer neuen Browserseite geöffnet", ); +my %hset = ( # Hash für Set-Funktion (needcred => 1: Funktion benötigt gesetzte Credentials) + credentials => { fn => "_setcredentials", needcred => 0 }, + smtpcredentials => { fn => "_setsmtpcredentials", needcred => 0 }, + on => { fn => "_seton", needcred => 1 }, + off => { fn => "_setoff", needcred => 1 }, + snap => { fn => "_setsnap", needcred => 1 }, + snapCams => { fn => "_setsnapCams", needcred => 1 }, + startTracking => { fn => "_setstartTracking", needcred => 1 }, + stopTracking => { fn => "_setstopTracking", needcred => 1 }, + setZoom => { fn => "_setsetZoom", needcred => 1 }, + snapGallery => { fn => "_setsnapGallery", needcred => 1 }, + createSnapGallery => { fn => "_setcreateSnapGallery", needcred => 1 }, + createPTZcontrol => { fn => "_setcreatePTZcontrol", needcred => 1 }, + createStreamDev => { fn => "_setcreateStreamDev", needcred => 1 }, + createReadingsGroup => { fn => "_setcreateReadingsGroup", needcred => 1 }, + enable => { fn => "_setenable", needcred => 1 }, + disable => { fn => "_setdisable", needcred => 1 }, + motdetsc => { fn => "_setmotdetsc", needcred => 1 }, + expmode => { fn => "_setexpmode", needcred => 1 }, + homeMode => { fn => "_sethomeMode", needcred => 1 }, + autocreateCams => { fn => "_setautocreateCams", needcred => 1 }, + goPreset => { fn => "_setgoPreset", needcred => 1 }, + optimizeParams => { fn => "_setoptimizeParams", needcred => 1 }, + pirSensor => { fn => "_setpirSensor", needcred => 1 }, + runPatrol => { fn => "_setrunPatrol", needcred => 1 }, + goAbsPTZ => { fn => "_setgoAbsPTZ", needcred => 1 }, + move => { fn => "_setmove", needcred => 1 }, + runView => { fn => "_setrunView", needcred => 1 }, + hlsreactivate => { fn => "_sethlsreactivate", needcred => 1 }, + hlsactivate => { fn => "_sethlsactivate", needcred => 1 }, + refresh => { fn => "_setrefresh", needcred => 0 }, + extevent => { fn => "_setextevent", needcred => 1 }, + stopView => { fn => "_setstopView", needcred => 1 }, + setPreset => { fn => "_setsetPreset", needcred => 1 }, + setHome => { fn => "_setsetHome", needcred => 1 }, + delPreset => { fn => "_setdelPreset", needcred => 1 }, +); + +my %hget = ( # Hash für Get-Funktion (needcred => 1: Funktion benötigt gesetzte Credentials) + apiInfo => { fn => "_getapiInfo", needcred => 1 }, + caminfo => { fn => "_getcaminfo", needcred => 1 }, + caminfoall => { fn => "_getcaminfoall", needcred => 1 }, + homeModeState => { fn => "_gethomeModeState", needcred => 1 }, + listLog => { fn => "_getlistLog", needcred => 1 }, + listPresets => { fn => "_getlistPresets", needcred => 1 }, + saveRecording => { fn => "_getsaveRecording", needcred => 1 }, + svsinfo => { fn => "_getsvsinfo", needcred => 1 }, + storedCredentials => { fn => "_getstoredCredentials", needcred => 1 }, + snapGallery => { fn => "_getsnapGallery", needcred => 1 }, + snapinfo => { fn => "_getsnapinfo", needcred => 1 }, + snapfileinfo => { fn => "_getsnapfileinfo", needcred => 1 }, + eventlist => { fn => "_geteventlist", needcred => 1 }, + stmUrlPath => { fn => "_getstmUrlPath", needcred => 1 }, + scanVirgin => { fn => "_getscanVirgin", needcred => 1 }, + versionNotes => { fn => "_getversionNotes", needcred => 1 }, +); + +my %hparse = ( # Hash der Opcode Parse Funktionen + Start => { fn => "_parseStart", }, + Stop => { fn => "_parseStop", }, + GetRec => { fn => "_parseGetRec", }, + MotDetSc => { fn => "_parseMotDetSc", }, + getsvslog => { fn => "_parsegetsvslog", }, + SaveRec => { fn => "_parseSaveRec", }, + gethomemodestate => { fn => "_parsegethomemodestate", }, + getPresets => { fn => "_parsegetPresets", }, + Snap => { fn => "_parseSnap", }, + getsvsinfo => { fn => "_parsegetsvsinfo", }, + runliveview => { fn => "_parserunliveview", }, + getStmUrlPath => { fn => "_parsegetStmUrlPath", }, + Getcaminfo => { fn => "_parseGetcaminfo", }, + Getptzlistpatrol => { fn => "_parseGetptzlistpatrol", }, + Getptzlistpreset => { fn => "_parseGetptzlistpreset", }, + Getcapabilities => { fn => "_parseGetcapabilities", }, + getmotionenum => { fn => "_parsegetmotionenum", }, + geteventlist => { fn => "_parsegeteventlist", }, + gopreset => { fn => "_parsegopreset", }, + getsnapinfo => { fn => "_parsegetsnapinfo", }, + getsnapgallery => { fn => "_parsegetsnapgallery", }, +); + +my %hdt = ( # Delta Timer Hash für Zeitsteuerung der Funktionen + __camSnap => 0.2, # ab hier hohe Prio + __camStartRec => 0.3, + __camStopRec => 0.3, + __startTrack => 0.3, + __stopTrack => 0.3, + __moveStop => 0.3, + __doPtzAaction => 0.4, + __setZoom => 0.4, + __getSnapFilename => 0.5, + __runLiveview => 0.5, + __stopLiveview => 0.5, + __extEvent => 0.5, + __camEnable => 0.5, + __camDisable => 0.5, + __setOptParams => 0.6, # ab hier mittlere Prio + __setHomeMode => 0.6, + __getHomeModeState => 0.7, + __getRec => 0.7, + __getCapabilities => 0.7, + __activateHls => 0.7, + __reactivateHls => 0.7, + __getSvsLog => 0.8, + __getRecAndSave => 0.9, + __getSvsInfo => 1.0, + __camExpmode => 1.1, + __getPresets => 1.2, + __setPreset => 1.2, + __setHome => 1.2, + __managePir => 1.2, + __delPreset => 1.4, + __getStreamFormat => 1.4, + __getSnapInfo => 1.7, # ab hier niedrige Prio + __camMotDetSc => 1.8, + __getStmUrlPath => 2.0, + __getEventList => 2.0, + __getMotionEnum => 2.0, + __getPtzPresetList => 2.0, + __getPtzPatrolList => 2.0, + __getCamInfo => 2.0, + __camAutocreate => 2.1, + __sessionOff => 2.7, + __getApiInfo => 2.7, +); + my %imc = ( # disbled String modellabhängig (SVS / CAM) 0 => { 0 => "initialized", 1 => "inactive" }, 1 => { 0 => "off", 1 => "inactive" }, ); +my %hexmo = ( # Hash Exposure Modes + auto => 0, + day => 1, + night => 2, +); + +my %hrkeys = ( # Hash der möglichen Response Keys + camLiveMode => { 0 => "Liveview from DS", 1 => "Liveview from Camera", }, + source => { -1 => "disabled", 0 => "Camera", 1 => "SVS", }, + deviceType => { 1 => "Camera", 2 => "Video_Server", 4 => "PTZ", 8 => "Fisheye", }, + camStatus => { 1 => "enabled", 2 => "deleted", 3 => "disconnected", 4 => "unavailable", 5 => "ready", 6 => "inaccessible", 7 => "disabled", 8 => "unrecognized", 9 => "setting", 10 => "Server disconnected", 11 => "migrating", 12 => "others", 13 => "Storage removed", 14 => "stopping", 15 => "Connect hist failed", 16 => "unauthorized", 17 => "RTSP error", 18 => "No video", }, + exposure_control => { 0 => "Auto", 1 => "50HZ", 2 => "60HZ", 3 => "Hold", 4 => "Outdoor", 5 => "None", 6 => "Unknown", }, + camAudioType => { 0 => "Unknown", 1 => "PCM", 2 => "G711", 3 => "G726", 4 => "AAC", 5 => "AMR", }, + exposure_mode => { 0 => "Auto", 1 => "Day", 2 => "Night", 3 => "Schedule", 4 => "Unknown", }, + userPriv => { 0 => "No Access", 1 => "Admin", 2 => "Manager", 4 => "Viewer", FF => "All", }, + ptzFocus => { 0 => "false", 1 => "support step operation", 2 => "support continuous operation", }, + ptzTilt => { 0 => "false", 1 => "support step operation", 2 => "support continuous operation", }, + ptzZoom => { 0 => "false", 1 => "support step operation", 2 => "support continuous operation", }, + ptzPan => { 0 => "false", 1 => "support step operation", 2 => "support continuous operation", }, + ptzIris => { 0 => "false", 1 => "support step operation", 2 => "support continuous operation", }, +); + my %zd = ( # Hash der Zoomsteuerung ".++" => {dir => "in", sttime => 6, moveType => "Start", panimg => "Zoom_in_wide_w.png", }, "+" => {dir => "in", sttime => 0.5, moveType => "Start", panimg => "Zoom_in_w.png", }, @@ -465,10 +627,12 @@ my %sdswfn = ( # Fun # Standardvariablen und Forward-Deklaration my $defSlim = 3; # default Anzahl der abzurufenden Schnappschüsse mit snapGallery +my $defColumns = 3; # default Anzahl der Spalten einer snapGallery my $defSnum = "1,2,3,4,5,6,7,8,9,10"; # mögliche Anzahl der abzurufenden Schnappschüsse mit snapGallery my $compstat = "8.2.8"; # getestete SVS-Version my $valZoom = ".++,+,stop,-,--."; # Inhalt des Setters "setZoom" my $shutdownInProcess = 0; # Statusbit shutdown +my $todef = 20; # httptimeout default Wert #use vars qw($FW_ME); # webname (default is fhem), used by 97_GROUP/weblink #use vars qw($FW_subdir); # Sub-path in URL, used by FLOORPLAN/weblink @@ -526,7 +690,7 @@ my %vHintsExt_en = ( "

", "4" => "The message Meldung \"WARNING - The current/simulated SVS-version ... may be incompatible with SSCam version...\" means that ". "the used SSCam version was currently not tested or (partially) incompatible with the installed version of Synology Surveillance Station (Reading \"SVSversion\"). ". - "The compatible SVS-Version is printed out in the Internal COMPATIBILITY.\n". + "The compatible SVS-Version is printed out in the Internal COMPATIBILITY.
". "Actions: At first please update your SSCam version. If the message does appear furthermore, please inform the SSCam Maintainer. ". "To ignore this message temporary, you may reduce the verbose level of your SSCam device. ". "

", @@ -592,7 +756,7 @@ my %vHintsExt_de = ( "

", "4" => "Die Meldung \"WARNING - The current/simulated SVS-version ... may be incompatible with SSCam version...\" ist ein Hinweis darauf, dass ". "die eingesetzte SSCam Version noch nicht mit der verwendeten Version von Synology Surveillance Station (Reading \"SVSversion\") getestet ". - "wurde oder (teilweise) mit dieser Version nicht kompatibel ist. Die kompatible SVS-Version ist im Internal COMPATIBILITY ersichtlich.\n". + "wurde oder (teilweise) mit dieser Version nicht kompatibel ist. Die kompatible SVS-Version ist im Internal COMPATIBILITY ersichtlich.
". "Maßnahmen: Bitte SSCam zunächst updaten. Sollte die Meldung weiterhin auftreten, bitte den SSCam Maintainer informieren. Zur ". "vorübergehenden Ignorierung kann der verbose Level des SSCam-Devices entsprechend reduziert werden. ". "

", @@ -679,11 +843,10 @@ sub Define { # define CamCP1 SSCAM Carport 192.168.2.20 [5000] # ($hash) [1] [2] [3] [4] # - my $hash = shift; - my $def = shift; - my $name = $hash->{NAME}; + my ($hash, $def) = @_; + my $name = $hash->{NAME}; - return "Error: Perl module ".$SScamMMDBI." is missing. Install it on Debian with: sudo apt-get install libjson-perl" if($SScamMMDBI); + return "Error: Perl module ".$MMJSON." is missing. Install it on Debian with: sudo apt-get install libjson-perl" if($MMJSON); my @a = split m{\s+}x, $def; @@ -703,32 +866,12 @@ sub Define { $hash->{PROTOCOL} = $proto; $hash->{COMPATIBILITY} = $compstat; # getestete SVS-version Kompatibilität $hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden - - # benötigte API's in $hash einfügen - $hash->{HELPER}{APIINFO} = "SYNO.API.Info"; # Info-Seite für alle API's, einzige statische Seite ! - $hash->{HELPER}{APIAUTH} = "SYNO.API.Auth"; # API used to perform session login and logout - $hash->{HELPER}{APISVSINFO} = "SYNO.SurveillanceStation.Info"; - $hash->{HELPER}{APIEVENT} = "SYNO.SurveillanceStation.Event"; - $hash->{HELPER}{APIEXTREC} = "SYNO.SurveillanceStation.ExternalRecording"; - $hash->{HELPER}{APIEXTEVT} = "SYNO.SurveillanceStation.ExternalEvent"; - $hash->{HELPER}{APICAM} = "SYNO.SurveillanceStation.Camera"; # stark geändert ab API v2.8 - $hash->{HELPER}{APISNAPSHOT} = "SYNO.SurveillanceStation.SnapShot"; # This API provides functions on snapshot, including taking, editing and deleting snapshots. - $hash->{HELPER}{APIPTZ} = "SYNO.SurveillanceStation.PTZ"; - $hash->{HELPER}{APIPRESET} = "SYNO.SurveillanceStation.PTZ.Preset"; - $hash->{HELPER}{APICAMEVENT} = "SYNO.SurveillanceStation.Camera.Event"; - $hash->{HELPER}{APIVIDEOSTM} = "SYNO.SurveillanceStation.VideoStreaming"; # verwendet in Response von "SYNO.SurveillanceStation.Camera: GetLiveViewPath" -> StreamKey-Methode - # $hash->{HELPER}{APISTM} = "SYNO.SurveillanceStation.Streaming"; # provides methods to get Live View or Event video stream, removed in API v2.8 - $hash->{HELPER}{APISTM} = "SYNO.SurveillanceStation.Stream"; # Beschreibung ist falsch und entspricht "SYNO.SurveillanceStation.Streaming" auch noch ab v2.8 - $hash->{HELPER}{APIHM} = "SYNO.SurveillanceStation.HomeMode"; - $hash->{HELPER}{APILOG} = "SYNO.SurveillanceStation.Log"; - $hash->{HELPER}{APIAUDIOSTM} = "SYNO.SurveillanceStation.AudioStream"; # Audiostream mit SID, removed in API v2.8 (noch undokumentiert verfügbar) - $hash->{HELPER}{APIVIDEOSTMS} = "SYNO.SurveillanceStation.VideoStream"; # Videostream mit SID, removed in API v2.8 (noch undokumentiert verfügbar) - $hash->{HELPER}{APIREC} = "SYNO.SurveillanceStation.Recording"; # This API provides method to query recording information. - + # Startwerte setzen if(IsModelCam($hash)) { # initiale Webkommandos setzen $attr{$name}{webCmd} = "on:off:snap:enable:disable:runView:stopView"; - } else { + } + else { $attr{$name}{webCmd} = "homeMode"; $attr{$name}{webCmdLabel} = "HomeMode"; } @@ -737,26 +880,35 @@ sub Define { $hash->{HELPER}{OLDVALPOLL} = "0"; $hash->{HELPER}{RECTIME_DEF} = "15"; # Standard für rectime setzen, überschreibbar durch Attribut "rectime" bzw. beim "set .. on-for-time" $hash->{HELPER}{OLDPTZHOME} = ""; - $hash->{".ptzhtml"} = ""; + $hash->{".ptzhtml"} = ""; # initial -> es wird ptzpanel neu eingelesen $hash->{HELPER}{HLSSTREAM} = "inactive"; # Aktivitätsstatus HLS-Streaming $hash->{HELPER}{SNAPLIMIT} = 0; # abgerufene Anzahl Snaps $hash->{HELPER}{TOTALCNT} = 0; # totale Anzahl Snaps - # Versionsinformationen setzen - setVersionInfo($hash); + my $params = { + hash => $hash, + notes => \%vNotesIntern, + useAPI => 1, + useSMUtils => 1, + useErrCodes => 1 + }; + use version 0.77; our $VERSION = moduleVersion ($params); # Versionsinformationen setzen - readingsBeginUpdate ($hash); + readingsBeginUpdate ($hash ); readingsBulkUpdate ($hash, "PollState", "Inactive"); # es ist keine Gerätepolling aktiv + if(IsModelCam($hash)) { readingsBulkUpdate ($hash, "Availability", "???"); # Verfügbarkeit ist unbekannt readingsBulkUpdate ($hash, "state", "off"); # Init für "state" , Problemlösung für setstate, Forum #308 - } else { + } + else { readingsBulkUpdate ($hash, "state", "Initialized"); # Init für "state" wenn SVS } - readingsEndUpdate($hash,1); - getCredentials($hash,1, "svs" ); # Credentials lesen und in RAM laden ($boot=1) - getCredentials($hash,1, "smtp"); + readingsEndUpdate ($hash,1); + + getCredentials ($hash,1, "credentials" ); # Credentials lesen und in RAM laden ($boot=1) + getCredentials ($hash,1, "SMTPcredentials"); # initiale Routinen zufällig verzögert nach Restart ausführen RemoveInternalTimer ($hash, "FHEM::SSCam::initOnBoot" ); @@ -796,11 +948,11 @@ sub delayedShutdown { my $hash = shift; my $name = $hash->{NAME}; - $shutdownInProcess = 1; # Statusbit shutdown setzen -> getApiSites wird nicht mehr ausgeführt + $shutdownInProcess = 1; # Statusbit shutdown setzen -> asynchrone Funktionen nicht mehr ausgeführen Log3($name, 2, "$name - Quit session due to shutdown ..."); - sessionOff($hash); + __sessionOff($hash); if($hash->{HELPER}{CACHEKEY}) { cache($name, "c_destroy"); @@ -831,1274 +983,12 @@ sub Delete { CommandDelete($hash->{CL},"$sgdev"); CommandDelete($hash->{CL},"TYPE=SSCamSTRM:FILTER=PARENT=$name"); # alle zugeordneten Streaming-Devices löschen falls vorhanden - - delete $data{SSCam}{$name}; # internen Cache löschen + + delete $data{SSCam}{$name}; # internen Cache löschen return; } -################################################################ -sub Attr { - my ($cmd,$name,$aName,$aVal) = @_; - my $hash = $defs{$name}; - - my ($do,$val,$cache); - - # $cmd can be "del" or "set" - # $name is device name - # aName and aVal are Attribute name and value - - if ($aName eq "session") { - delete $hash->{HELPER}{SID}; - } - - if ($aName =~ /hlsNetScript/x && IsModelCam($hash)) { - return " The attribute \"$aName\" is only valid for devices of type \"SVS\"! Please set this attribute in a device of this type."; - } - - if ($aName =~ /snapReadingRotate/x && !IsModelCam($hash)) { - return " The attribute \"$aName\" is not valid for devices of type \"SVS\"!."; - } - - # dynamisch PTZ-Attribute setzen (wichtig beim Start wenn Reading "DeviceType" nicht gesetzt ist) - if ($cmd eq "set" && ($aName =~ m/ptzPanel_/x)) { - for my $n (0..9) { - $n = sprintf("%2.2d",$n); - addToDevAttrList($name, "ptzPanel_row$n"); - } - addToDevAttrList($name, "ptzPanel_iconPrefix"); - addToDevAttrList($name, "ptzPanel_iconPath"); - } - - if($aName =~ m/ptzPanel_row|ptzPanel_Home|ptzPanel_use/x) { - InternalTimer(gettimeofday()+0.7, "FHEM::SSCam::addptzattr", "$name", 0); - } - - if ($aName eq "disable") { - my $iscam = IsModelCam($hash); - if($cmd eq "set") { - $do = ($aVal) ? 1 : 0; - } - $do = 0 if($cmd eq "del"); - if(IsModelCam($hash)) { - $val = ($do == 1 ? "inactive" : "off"); - } else { - $val = ($do == 1 ? "inactive" : "initialized"); - } - - if ($do == 1) { - RemoveInternalTimer($hash); - } else { - InternalTimer(gettimeofday()+int(rand(30)), "FHEM::SSCam::initOnBoot", $hash, 0); - } - - readingsSingleUpdate($hash, "state", $imc{$iscam}{$do}, 1); - readingsSingleUpdate($hash, "PollState", "Inactive", 1) if($do == 1); - readingsSingleUpdate($hash, "Availability", "???", 1) if($do == 1 && IsModelCam($hash)); - } - - if($aName =~ m/cacheType/) { - my $type = AttrVal($name,"cacheType","internal"); - if($cmd eq "set") { - if($aVal ne "internal") { - if($SScamMMCHI) { - return "Perl cache module ".$SScamMMCHI." is missing. You need to install it with the FHEM Installer for example."; - } - if($aVal eq "redis") { - if($SScamMMCHIRedis) { - return "Perl cache module ".$SScamMMCHIRedis." is missing. You need to install it with the FHEM Installer for example."; - } - if(!AttrVal($name,"cacheServerParam","")) { - return "For cacheType \"$aVal\" you must set first attribute \"cacheServerParam\" for Redis server connection: :"; - } - } - if($aVal eq "file") { - if($SScamMMCacheCache) { - return "Perl cache module ".$SScamMMCacheCache." is missing. You need to install it with the FHEM Installer for example."; - } - } - } - if ($aVal ne $type) { - if($hash->{HELPER}{CACHEKEY}) { - cache($name, "c_destroy"); # CHI-Cache löschen/entfernen - } else { - delete $data{SSCam}{$name}; # internen Cache löschen - } - } - } else { - if($hash->{HELPER}{CACHEKEY}) { - cache($name, "c_destroy"); # CHI-Cache löschen/entfernen - } - } - } - - if ($aName eq "showStmInfoFull") { - if($cmd eq "set") { - $do = ($aVal) ? 1 : 0; - } - $do = 0 if($cmd eq "del"); - - if ($do == 0) { - delete($defs{$name}{READINGS}{StmKeymjpegHttp}); - delete($defs{$name}{READINGS}{LiveStreamUrl}); - delete($defs{$name}{READINGS}{StmKeyUnicst}); - delete($defs{$name}{READINGS}{StmKeyUnicstOverHttp}); - delete($defs{$name}{READINGS}{StmKeymxpegHttp}); - } - } - - if ($aName eq "snapGallerySize") { - if($cmd eq "set") { - $do = ($aVal eq "Icon")?1:2; - } - $do = 0 if($cmd eq "del"); - - if ($do == 0) { - delete($hash->{HELPER}{".SNAPHASH"}) if(AttrVal($name,"snapGalleryBoost",0)); # Snaphash nur löschen wenn Snaps gepollt werden - Log3($name, 4, "$name - Snapshot hash deleted"); - } elsif (AttrVal($name,"snapGalleryBoost",0)) { - # snap-Infos abhängig ermitteln wenn gepollt werden soll - my ($slim,$ssize); - $hash->{HELPER}{GETSNAPGALLERY} = 1; - $slim = AttrVal($name,"snapGalleryNumber",$defSlim); # Anzahl der abzurufenden Snaps - $ssize = $do; - RemoveInternalTimer ($hash, "FHEM::SSCam::getSnapInfo" ); - InternalTimer (gettimeofday()+0.7, "FHEM::SSCam::getSnapInfo", "$name:$slim:$ssize", 0); - } - } - - if ($aName eq "snapGalleryBoost") { - if($cmd eq "set") { - $do = ($aVal == 1) ? 1 : 0; - } - $do = 0 if($cmd eq "del"); - - if ($do == 0) { - delete($hash->{HELPER}{".SNAPHASH"}); # Snaphash löschen - Log3($name, 4, "$name - Snapshot hash deleted"); - - } else { - # snapgallery regelmäßig neu einlesen wenn Polling ein - return "When you want activate \"snapGalleryBoost\", you have to set the attribute \"pollcaminfoall\" first because of the functionality depends on retrieving snapshots periodical." - if(!AttrVal($name,"pollcaminfoall",0)); - - my ($slim,$ssize); - $hash->{HELPER}{GETSNAPGALLERY} = 1; - $slim = AttrVal($name, "snapGalleryNumber", $defSlim); # Anzahl der abzurufenden Snaps - my $sg = AttrVal($name, "snapGallerySize", "Icon" ); # Auflösung Image - $ssize = ($sg eq "Icon") ? 1 : 2; - RemoveInternalTimer ($hash, "FHEM::SSCam::getSnapInfo" ); - InternalTimer (gettimeofday()+0.7, "FHEM::SSCam::getSnapInfo", "$name:$slim:$ssize", 0); - } - } - - if ($aName eq "snapGalleryNumber" && AttrVal($name,"snapGalleryBoost",0)) { - my ($slim,$ssize); - if($cmd eq "set") { - $do = ($aVal != 0) ? 1 : 0; - } - $do = 0 if($cmd eq "del"); - - if ($do == 0) { - $slim = 3; - } else { - $slim = $aVal; - } - - delete($hash->{HELPER}{".SNAPHASH"}); # bestehenden Snaphash löschen - $hash->{HELPER}{GETSNAPGALLERY} = 1; - my $sg = AttrVal($name,"snapGallerySize","Icon"); # Auflösung Image - $ssize = ($sg eq "Icon")?1:2; - RemoveInternalTimer ($hash, "FHEM::SSCam::getSnapInfo" ); - InternalTimer (gettimeofday()+0.7, "FHEM::SSCam::getSnapInfo", "$name:$slim:$ssize", 0); - } - - if ($aName eq "snapReadingRotate") { - if($cmd eq "set") { - $do = ($aVal) ? 1 : 0; - } - $do = 0 if($cmd eq "del"); - if(!$do) {$aVal = 0} - for my $i (1..10) { - if($i>$aVal) { - readingsDelete($hash, "LastSnapFilename$i" ); - readingsDelete($hash, "LastSnapId$i" ); - readingsDelete($hash, "LastSnapTime$i" ); - } - } - } - - if ($aName eq "simu_SVSversion") { - delete $hash->{HELPER}{APIPARSET}; - delete $hash->{HELPER}{SID}; - delete $hash->{CAMID}; - RemoveInternalTimer ($hash, "FHEM::SSCam::getCaminfoAll" ); - InternalTimer (gettimeofday()+0.5, "FHEM::SSCam::getCaminfoAll", $hash, 0); - } - - if($aName =~ m/pollcaminfoall/ && $init_done == 1) { - RemoveInternalTimer ($hash, "FHEM::SSCam::getCaminfoAll" ); - InternalTimer (gettimeofday()+1.0, "FHEM::SSCam::getCaminfoAll", $hash, 0); - RemoveInternalTimer ($hash, "FHEM::SSCam::wdpollcaminfo" ); - InternalTimer (gettimeofday()+1.5, "FHEM::SSCam::wdpollcaminfo", $hash, 0); - } - - if($aName =~ m/pollnologging/ && $init_done == 1) { - RemoveInternalTimer ($hash, "FHEM::SSCam::wdpollcaminfo" ); - InternalTimer (gettimeofday()+1.0, "FHEM::SSCam::wdpollcaminfo", $hash, 0); - } - - if ($cmd eq "set") { - if ($aName =~ m/httptimeout|snapGalleryColumns|rectime|pollcaminfoall/x) { - unless ($aVal =~ /^\d+$/x) { return " The Value for $aName is not valid. Use only figures 1-9 !";} - } - if($aName =~ m/pollcaminfoall/x) { - return "The value of \"$aName\" has to be greater than 10 seconds." if($aVal <= 10); - } - if($aName =~ m/cacheServerParam/x) { - return "Please provide the Redis server parameters in form: : or unix:" if($aVal !~ /:\d+$|unix:.+$/x); - my $type = AttrVal($name, "cacheType", "internal"); - if($hash->{HELPER}{CACHEKEY} && $type eq "redis") { - cache($name, "c_destroy"); - } - } - if($aName =~ m/snapChatTxt|recChatTxt/x) { - return "When you want activate \"$aName\", you have to set first the attribute \"videofolderMap\" to the root folder ". - "of recordings and snapshots provided by an URL.\n". - "Example: http://server.domain:8081/surveillance " - if(!AttrVal($name, "videofolderMap", "") && $init_done == 1); - - } - } - - if ($cmd eq "del") { - if ($aName =~ m/pollcaminfoall/x) { - # Polling nicht ausschalten wenn snapGalleryBoost ein (regelmäßig neu einlesen) - return "Please switch off \"snapGalleryBoost\" first if you want to deactivate \"pollcaminfoall\" because the functionality of \"snapGalleryBoost\" depends on retrieving snapshots periodical." - if(AttrVal($name,"snapGalleryBoost",0)); - } - } - -return; -} - -################################################################ -sub Set { - my ($hash, @a) = @_; - return "\"set X\" needs at least an argument" if ( @a < 2 ); - my $name = $a[0]; - my $opt = $a[1]; - my $prop = $a[2]; - my $prop1 = $a[3]; - my $prop2 = $a[4]; - my $prop3 = $a[5]; - my $camname = $hash->{CAMNAME}; - my $success; - my $setlist; - - return if(IsDisabled($name)); - - if(!$hash->{CREDENTIALS}) { - # initiale setlist für neue Devices - $setlist = "Unknown argument $opt, choose one of ". - "credentials " - ; - } elsif(IsModelCam($hash)) { - # selist für Cams - my $hlslfw = IsCapHLS($hash) ? ",live_fw_hls," : ","; - $setlist = "Unknown argument $opt, choose one of ". - "credentials ". - "smtpcredentials ". - "expmode:auto,day,night ". - "on ". - "off:noArg ". - "motdetsc:disable,camera,SVS ". - "snap ". - (AttrVal($name, "snapGalleryBoost",0) ? (AttrVal($name,"snapGalleryNumber",undef) || AttrVal($name,"snapGalleryBoost",0)) ? "snapGallery:noArg " : "snapGallery:$defSnum " : " "). - "createReadingsGroup ". - "createSnapGallery:noArg ". - "createStreamDev:generic,hls,lastsnap,mjpeg,switched ". - "enable:noArg ". - "disable:noArg ". - "optimizeParams ". - "runView:live_fw".$hlslfw."live_link,live_open,lastrec_fw,lastrec_fw_MJPEG,lastrec_fw_MPEG4/H.264,lastrec_open,lastsnap_fw ". - "stopView:noArg ". - (IsCapPTZObjTrack($hash) ? "startTracking:noArg " : ""). - (IsCapPTZObjTrack($hash) ? "stopTracking:noArg " : ""). - (IsCapPTZPan($hash) ? "setPreset ": ""). - (IsCapPTZPan($hash) ? "setHome:---currentPosition---,".ReadingsVal("$name","Presets","")." " : ""). - (IsCapPTZPan($hash) ? "delPreset:".ReadingsVal("$name","Presets","")." " : ""). - (IsCapPTZPan($hash) ? "runPatrol:".ReadingsVal("$name", "Patrols", "")." " : ""). - (IsCapPTZPan($hash) ? "goPreset:".ReadingsVal("$name", "Presets", "")." " : ""). - (IsCapPTZ($hash) ? "createPTZcontrol:noArg " : ""). - (IsCapPTZAbs($hash) ? "goAbsPTZ"." " : ""). - (IsCapPTZDir($hash) ? "move"." " : ""). - (IsCapPIR($hash) ? "pirSensor:activate,deactivate " : ""). - (IsCapZoom($hash) ? "setZoom:$valZoom " : ""). - ""; - } else { - # setlist für SVS Devices - $setlist = "Unknown argument $opt, choose one of ". - "autocreateCams:noArg ". - "credentials ". - "createStreamDev:master ". - "smtpcredentials ". - "createReadingsGroup ". - "extevent:1,2,3,4,5,6,7,8,9,10 ". - ($hash->{HELPER}{APIHMMAXVER} ? "homeMode:on,off " : ""). - "snapCams "; - } - - if ($opt eq "credentials") { - return "Credentials are incomplete, use username password" if (!$prop || !$prop1); - return "Password is too long. It is limited up to and including 20 characters." if (length $prop1 > 20); - delete $hash->{HELPER}{SID}; - ($success) = setCredentials($hash,"svs",$prop,$prop1); - $hash->{HELPER}{ACTIVE} = "off"; - - if($success) { - getCaminfoAll($hash,0); - RemoveInternalTimer($hash, "FHEM::SSCam::getPtzPresetList"); - InternalTimer(gettimeofday()+11, "FHEM::SSCam::getPtzPresetList", $hash, 0); - RemoveInternalTimer($hash, "FHEM::SSCam::getPtzPatrolList"); - InternalTimer(gettimeofday()+12, "FHEM::SSCam::getPtzPatrolList", $hash, 0); - versionCheck($hash); - return "Username and Password saved successfully"; - } else { - return "Error while saving Username / Password - see logfile for details"; - } - - } - - if ($opt eq "smtpcredentials") { - return "Credentials are incomplete, use username password" if (!$prop || !$prop1); - ($success) = setCredentials($hash,"smtp",$prop,$prop1); - - if($success) { - return "SMTP-Username and SMTP-Password saved successfully"; - } else { - return "Error while saving SMTP-Username / SMTP-Password - see logfile for details"; - } - } - - if ($opt eq "on" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - if (defined($prop) && $prop =~ /^\d+$/x) { - $hash->{HELPER}{RECTIME_TEMP} = $prop; - } - - my $spec = join(" ",@a); - if($spec =~ /STRM:/x) { - my ($inf) = $spec =~ m/STRM:(.*)/ix; # Aufnahme durch SSCamSTRM-Device - $hash->{HELPER}{INFORM} = $inf; - } - - my $emtxt = AttrVal($name, "recEmailTxt", ""); - if($spec =~ /recEmailTxt:/x) { - ($emtxt) = $spec =~ m/recEmailTxt:"(.*)"/xi; - } - - if($emtxt) { - # Recording soll nach Erstellung per Email versendet werden - # recEmailTxt muss sein: subject => , body => - if (!$hash->{SMTPCREDENTIALS}) {return "Due to \"recEmailTxt\" is set, you want to send recordings by email but SMTP credentials are not set - make sure you've set credentials with \"set $name smtpcredentials username password\"";} - $hash->{HELPER}{SMTPRECMSG} = $emtxt; - } - - my $teletxt = AttrVal($name, "recTelegramTxt", ""); - if($spec =~ /recTelegramTxt:/x) { - ($teletxt) = $spec =~ m/recTelegramTxt:"(.*)"/xi; - } - - if ($teletxt) { - # Recording soll nach Erstellung per TelegramBot versendet werden - # Format $teletxt muss sein: recTelegramTxt:"tbot => , peers => , subject => " - $hash->{HELPER}{TELERECMSG} = $teletxt; - } - - my $chattxt = AttrVal($name, "recChatTxt", ""); - if($spec =~ /recChatTxt:/x) { - ($chattxt) = $spec =~ m/recChatTxt:"(.*)"/xi; - } - - if ($chattxt) { - # Recording soll nach Erstellung per SSChatBot versendet werden - # Format $chattxt muss sein: recChatTxt:"chatbot => , peers => , subject => " - $hash->{HELPER}{CHATRECMSG} = $chattxt; - } - - camStartRec("$name!_!$emtxt!_!$teletxt!_!$chattxt"); - - } elsif ($opt eq "off" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - - my $spec = join(" ",@a); - if($spec =~ /STRM:/x) { - ($hash->{HELPER}{INFORM}) = $spec =~ m/STRM:(.*)/xi; # Aufnahmestop durch SSCamSTRM-Device - } - - camStopRec($hash); - - } elsif ($opt eq "snap" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - - my ($num,$lag,$ncount) = (1,2,1); - if($prop && $prop =~ /^\d+$/x) { # Anzahl der Schnappschüsse zu triggern (default: 1) - $num = $prop; - $ncount = $prop; - } - if($prop1 && $prop1 =~ /^\d+$/x) { # Zeit zwischen zwei Schnappschüssen (default: 2 Sekunden) - $lag = $prop1; - } - - Log3($name, 4, "$name - Trigger snapshots - Number: $num, Lag: $lag"); - - my $spec = join(" ",@a); - if($spec =~ /STRM:/x) { - ($hash->{HELPER}{INFORM}) = $spec =~ m/STRM:(.*)/xi; # Snap by SSCamSTRM-Device - } - - my $emtxt = AttrVal($name, "snapEmailTxt", ""); - if($spec =~ /snapEmailTxt:/x) { - ($emtxt) = $spec =~ m/snapEmailTxt:"(.*)"/xi; - } - - if ($emtxt) { - # Snap soll nach Erstellung per Email versendet werden - # Format $emtxt muss sein: snapEmailTxt:"subject => , body => " - if (!$hash->{SMTPCREDENTIALS}) {return "It seems you want to send snapshots by email but SMTP credentials are not set - make sure you've set credentials with \"set $name smtpcredentials username password\"";} - $hash->{HELPER}{SMTPMSG} = $emtxt; - } - - my $teletxt = AttrVal($name, "snapTelegramTxt", ""); - if($spec =~ /snapTelegramTxt:/x) { - ($teletxt) = $spec =~ m/snapTelegramTxt:"(.*)"/xi; - } - - if ($teletxt) { - # Snap soll nach Erstellung per TelegramBot versendet werden - # Format $teletxt muss sein: snapTelegramTxt:"tbot => , peers => , subject => " - $hash->{HELPER}{TELEMSG} = $teletxt; - } - - my $chattxt = AttrVal($name, "snapChatTxt", ""); - if($spec =~ /snapChatTxt:/x) { - ($chattxt) = $spec =~ m/snapChatTxt:"(.*)"/xi; - } - - if ($chattxt) { - # Snap soll nach Erstellung per SSChatBot versendet werden - # Format $chattxt muss sein: snapChatTxt:"chatbot => , peers => , subject => " - $hash->{HELPER}{CHATMSG} = $chattxt; - } - - camSnap("$name!_!$num!_!$lag!_!$ncount!_!$emtxt!_!$teletxt!_!$chattxt"); - - } elsif ($opt eq "snapCams" && !IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - - my ($num,$lag,$ncount) = (1,2,1); - my $cams = "all"; - if($prop && $prop =~ /^\d+$/x) { # Anzahl der Schnappschüsse zu triggern (default: 1) - $num = $prop; - $ncount = $prop; - } - if($prop1 && $prop1 =~ /^\d+$/x) { # Zeit zwischen zwei Schnappschüssen (default: 2 Sekunden) - $lag = $prop1; - } - - my $at = join(" ",@a); - if($at =~ /CAM:/xi) { - ($cams) = $at =~ m/CAM:"(.*)"/xi; - $cams =~ s/\s//gx; - } - - my @camdvs; - if($cams eq "all") { # alle nicht disabled Kameras auslösen, sonst nur die gewählten - @camdvs = devspec2array("TYPE=SSCam:FILTER=MODEL!=SVS"); - for (@camdvs) { - if($defs{$_} && !IsDisabled($_)) { - $hash->{HELPER}{ALLSNAPREF}{$_} = ""; # Schnappschuss Hash für alle Cams -> Schnappschußdaten sollen hinein - } - } - } else { - @camdvs = split(",",$cams); - for (@camdvs) { - if($defs{$_} && !IsDisabled($_)) { - $hash->{HELPER}{ALLSNAPREF}{$_} = ""; - } - } - } - - return "No valid camera devices are specified for trigger snapshots" if(!$hash->{HELPER}{ALLSNAPREF}); - - my $emtxt; - my $teletxt = ""; - my $rawet = AttrVal($name, "snapEmailTxt", ""); - my $bt = join(" ",@a); - if($bt =~ /snapEmailTxt:/x) { - ($rawet) = $bt =~ m/snapEmailTxt:"(.*)"/xi; - } - if($rawet) { - $hash->{HELPER}{CANSENDSNAP} = 1; # zentraler Schnappschußversand wird aktiviert - $hash->{HELPER}{SMTPMSG} = $rawet; - } - - my ($csnap,$cmail) = ("",""); - for my $key (keys%{$hash->{HELPER}{ALLSNAPREF}}) { - if(!AttrVal($key, "snapEmailTxt", "")) { - delete $hash->{HELPER}{ALLSNAPREF}->{$key}; # Snap dieser Kamera auslösen aber nicht senden - $csnap .= $csnap?", $key":$key; - $emtxt = ""; - } else { - $cmail .= $cmail?", $key":$key; - $emtxt = $rawet; - } - camSnap("$key!_!$num!_!$lag!_!$ncount!_!$emtxt!_!$teletxt"); - } - Log3($name, 4, "$name - Trigger snapshots by SVS - Number: $num, Lag: $lag, Snap only: \"$csnap\", Snap and send: \"$cmail\" "); - - - } elsif ($opt eq "startTracking" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - if ($hash->{HELPER}{APIPTZMAXVER} < 5) {return "Function \"$opt\" needs a higher version of Surveillance Station";} - startTrack($hash); - - } elsif ($opt eq "stopTracking" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - if ($hash->{HELPER}{APIPTZMAXVER} < 5) {return "Function \"$opt\" needs a higher version of Surveillance Station";} - stopTrack($hash); - - } elsif ($opt eq "setZoom" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - $prop = $prop // "+"; # Korrektur -> "+" in Taste wird als undef geliefert - $prop = ".++" if($prop eq "."); # Korrektur -> ".++" in Taste wird als "." geliefert - setZoom ("$name!_!$prop"); - - } elsif ($opt eq "snapGallery" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - my $ret = getClHash($hash); - return $ret if($ret); - - if(!AttrVal($name, "snapGalleryBoost",0)) { - # Snaphash ist nicht vorhanden und wird neu abgerufen und ausgegeben - $hash->{HELPER}{GETSNAPGALLERY} = 1; - - # snap-Infos für Gallerie abrufen - my ($sg,$slim,$ssize); - $slim = $prop?AttrVal($name,"snapGalleryNumber",$prop):AttrVal($name,"snapGalleryNumber",$defSlim); # Anzahl der abzurufenden Snapshots - $ssize = (AttrVal($name,"snapGallerySize","Icon") eq "Icon")?1:2; # Image Size 1-Icon, 2-Full - - getSnapInfo("$name:$slim:$ssize"); - - } else { - # Snaphash ist vorhanden und wird zur Ausgabe aufbereitet (Polling ist aktiv) - $hash->{HELPER}{SNAPLIMIT} = AttrVal($name,"snapGalleryNumber",$defSlim); - my %pars = ( linkparent => $name, - linkname => '', - ftui => 0 - ); - my $htmlCode = composeGallery(\%pars); - for (my $k=1; (defined($hash->{HELPER}{CL}{$k})); $k++ ) { - if ($hash->{HELPER}{CL}{$k}->{COMP}) { - # CL zusammengestellt (Auslösung durch Notify) - asyncOutput($hash->{HELPER}{CL}{$k}, "$htmlCode"); - } else { - # Output wurde über FHEMWEB ausgelöst - return $htmlCode; - } - } - delete($hash->{HELPER}{CL}); - } - - } elsif ($opt eq "createSnapGallery" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - my ($ret,$sgdev); - return qq{Before you can use "$opt", you must first set the "snapGalleryBoost" attribute, since automatic snapshot retrieval is required.} - if(!AttrVal($name,"snapGalleryBoost",0)); - $sgdev = "SSCamSTRM.$name.snapgallery"; - $ret = CommandDefine($hash->{CL},"$sgdev SSCamSTRM {FHEM::SSCam::composeGallery('$name','$sgdev','snapgallery')}"); - return $ret if($ret); - my $room = "SSCam"; - $attr{$sgdev}{room} = $room; - return qq{Snapgallery device "$sgdev" created and assigned to room "$room".}; - - } elsif ($opt eq "createPTZcontrol" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - my $ptzcdev = "SSCamSTRM.$name.PTZcontrol"; - my $ret = CommandDefine($hash->{CL},"$ptzcdev SSCamSTRM {FHEM::SSCam::ptzPanel('$name','$ptzcdev','ptzcontrol')}"); - return $ret if($ret); - my $room = AttrVal($name,"room","SSCam"); - $attr{$ptzcdev}{room} = $room; - $attr{$ptzcdev}{group} = $name."_PTZcontrol"; - return qq{PTZ control device "$ptzcdev" created and assigned to room "$room".}; - - } elsif ($opt eq "createStreamDev") { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - my ($livedev,$ret); - - if($prop =~ /mjpeg/x) { - $livedev = "SSCamSTRM.$name.mjpeg"; - $ret = CommandDefine($hash->{CL},"$livedev SSCamSTRM {FHEM::SSCam::streamDev('$name','$livedev','mjpeg')}"); - return $ret if($ret); - } - if($prop =~ /generic/x) { - $livedev = "SSCamSTRM.$name.generic"; - $ret = CommandDefine($hash->{CL},"$livedev SSCamSTRM {FHEM::SSCam::streamDev('$name','$livedev','generic')}"); - return $ret if($ret); - } - if($prop =~ /hls/x) { - $livedev = "SSCamSTRM.$name.hls"; - $ret = CommandDefine($hash->{CL},"$livedev SSCamSTRM {FHEM::SSCam::streamDev('$name','$livedev','hls')}"); - return $ret if($ret); - my $c = "The device needs to set attribute \"hlsStrmObject\" in camera device \"$name\" to a valid HLS videostream"; - CommandAttr($hash->{CL},"$livedev comment $c"); - } - if($prop =~ /lastsnap/x) { - $livedev = "SSCamSTRM.$name.lastsnap"; - $ret = CommandDefine($hash->{CL},"$livedev SSCamSTRM {FHEM::SSCam::streamDev('$name','$livedev','lastsnap')}"); - return $ret if($ret); - my $c = "The device shows the last snapshot of camera device \"$name\". \n". - "If you always want to see the newest snapshot, please set attribute \"pollcaminfoall\" in camera device \"$name\".\n". - "Set also attribute \"snapGallerySize = Full\" in camera device \"$name\" to retrieve snapshots in original resolution."; - CommandAttr($hash->{CL},"$livedev comment $c"); - } - if($prop =~ /switched/x) { - $livedev = "SSCamSTRM.$name.switched"; - $ret = CommandDefine($hash->{CL},"$livedev SSCamSTRM {FHEM::SSCam::streamDev('$name','$livedev','switched')}"); - return $ret if($ret); - } - if($prop =~ /master/x) { - $livedev = "SSCamSTRM.$name.master"; - $ret = CommandDefine($hash->{CL},"$livedev SSCamSTRM {FHEM::SSCam::streamDev('$name','$livedev','master')}"); - return $ret if($ret); - } - - my $room = AttrVal($name,"room","SSCam"); - $attr{$livedev}{room} = $room; - return "Livestream device \"$livedev\" created and assigned to room \"$room\"."; - - } elsif ($opt eq "createReadingsGroup") { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - my $rgdev = $prop?$prop:"RG.SSCam"; - - my $rgdef = '<%it_camera>,On/Offline>,< >,,< >,erkennung>,< >,,< >,(MB)>,< >,,< >,Modul>,< >,'."\n". - 'TYPE=SSCam:FILTER=MODEL!=SVS:Availability,< >,state,< >,!CamMotDetSc,< >,!CamLastRecTime,< >,!UsedSpaceMB,< >,!LastUpdateTime,< >,?!disable,< >,?!LSnap,?!LRec,?!Start,?!Stop'."\n". - '< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >'."\n". - '< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >'."\n". - '< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >'."\n". - '<%it_server>,On/Off>,< >,,< >, >,< >,< >,< >,< >, >,< >,< >,< >,< >,< >,< >,< >,< >,< >,< >'."\n". - 'TYPE=SSCam:FILTER=MODEL=SVS:!HomeModeState,< >,state,< >,< >,< >,< >,< >,< >, >,< >,< >,< >,?!disable,< >,< >,< >,< >,< >'."\n". - ''; - - my $ret = CommandDefine($hash->{CL},"$rgdev readingsGroup $rgdef"); - return $ret if($ret); - - my $room = AttrVal($name,"room","SSCam"); - CommandAttr($hash->{CL},"$rgdev room $room"); - CommandAttr($hash->{CL},"$rgdev alias Überblick Kameras"); - - my $cellStyle = '{'."\n". - ' "c:0" => \'style="text-align:left;font-weight:normal"\','."\n". - ' "c:1" => \'style="text-align:left;font-weight:normal"\','."\n". - ' "c:4" => \'style="text-align:center;font-weight:bold"\','."\n". - ' "c:5" => \'style="text-align:center;color:green;font-weight:normal"\','."\n". - ' "c:9" => \'style="text-align:center;font-weight:normal"\''."\n". - '}'; - CommandAttr($hash->{CL},"$rgdev cellStyle $cellStyle"); - - my $commands = '{'."\n". - ' "Availability.enabled" => "set $DEVICE disable",'."\n". - ' "Availability.disabled" => "set $DEVICE enable",'."\n". - ' "HomeModeState.on" => "set $DEVICE homeMode off",'."\n". - ' "HomeModeState.off" => "set $DEVICE homeMode on",'."\n". - ' "'.$rgdev.'.Start" => "set %DEVICE runView live_fw",'."\n". - ' "Start" => "set %DEVICE runView live_fw",'."\n". - ' "LRec" => "set %DEVICE runView lastrec_fw",'."\n". - ' "LSnap" => "set %DEVICE runView lastsnap_fw",'."\n". - ' "Stop" => "set %DEVICE stopView",'."\n". - ' "Record" => "runView:",'."\n". - ' "disable" => "disable:"'."\n". - '}'; - CommandAttr($hash->{CL},"$rgdev commands $commands"); - - my $nameStyle = 'style = "color:black;font-weight:bold;text-align:center"'; - CommandAttr($hash->{CL},"$rgdev nameStyle $nameStyle"); - - my $valueColumns = '{'."\n". - ' \'Wiedergabe\' => \'colspan="4"\''."\n". - '}'; - CommandAttr($hash->{CL},"$rgdev valueColumns $valueColumns"); - - my $valueFormat = '{'."\n". - ' ($READING eq "CamMotDetSc" && $VALUE eq "disabled") ? "external" : $VALUE'."\n". - '}'; - CommandAttr($hash->{CL},"$rgdev valueFormat $valueFormat"); - - my $valueIcon = '{'."\n". - ' "Availability.enabled" => "remotecontrol/black_btn_GREEN",'."\n". - ' "Availability.disabled" => "remotecontrol/black_btn_RED",'."\n". - ' "HomeModeState.on" => "status_available",'."\n". - ' "HomeModeState.off" => "status_away_1\@orange",'."\n". - ' "Start" => "black_btn_MJPEG",'."\n". - ' "LRec" => "black_btn_LASTRECIFRAME",'."\n". - ' "LSnap" => "black_btn_LSNAP",'."\n". - ' "Stop" => "remotecontrol/black_btn_POWEROFF3",'."\n". - ' "state.initialized" => "remotecontrol/black_btn_STOP",'."\n". - ' "state" => "%devStateIcon"'."\n". - '}'; - CommandAttr($hash->{CL},"$rgdev valueIcon $valueIcon"); - - my $valueStyle = '{'."\n". - ' if($READING eq "Availability" && $VALUE eq "enabled"){ \' style="color:green" \' }'."\n". - ' elsif( $READING eq "Availability" && $VALUE eq "disabled"){ \' style="color:red" \' }'."\n". - ' elsif( $READING eq "CamMotDetSc" && $VALUE =~ /SVS.*/ ){ \' style="color:orange" \' }'."\n". - ' elsif( $READING eq "CamMotDetSc" && $VALUE eq "disabled"){ \' style="color:LimeGreen" \' }'."\n". - ' elsif( $READING eq "CamMotDetSc" && $VALUE =~ /Cam.*/ ){ \' style="color:SandyBrown" \' }'."\n". - '}'; - CommandAttr($hash->{CL},"$rgdev valueStyle $valueStyle"); - - - return "readingsGroup device \"$rgdev\" created and assigned to room \"$room\"."; - - } elsif ($opt eq "enable" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - camEnable($hash); - - } elsif ($opt eq "disable" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - camDisable($hash); - - } elsif ($opt eq "motdetsc" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - if (!$prop || $prop !~ /disable|camera|SVS/x) { return qq{Command "$opt" needs one of those arguments: disable, camera, SVS !}; } - - $hash->{HELPER}{MOTDETSC} = $prop; - - if ($prop1) { - # check ob Zahl zwischen 1 und 99 - return "invalid value for sensitivity (SVS or camera) - use number between 1 - 99" if ($prop1 !~ /^[1-9]$|^[1-9][0-9]$/x); - $hash->{HELPER}{MOTDETSC_PROP1} = $prop1; - } - if ($prop2) { - # check ob Zahl zwischen 1 und 99 - return "invalid value for threshold (SVS) / object size (camera) - use number between 1 - 99" if ($prop1 !~ /^[1-9]$|^[1-9][0-9]$/x); - $hash->{HELPER}{MOTDETSC_PROP2} = $prop2; - } - if ($prop3) { - # check ob Zahl zwischen 1 und 99 - return "invalid value for threshold (SVS) / object size (camera) - use number between 1 - 99" if ($prop1 !~ /^[1-9]$|^[1-9][0-9]$/x); - $hash->{HELPER}{MOTDETSC_PROP3} = $prop3; - } - amMotDetSc($hash); - - } elsif ($opt eq "expmode" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - unless ($prop) { return " \"$opt\" needs one of those arguments: auto, day, night !";} - - $hash->{HELPER}{EXPMODE} = $prop; - camExpmode($hash); - - } elsif ($opt eq "homeMode" && !IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - unless ($prop) { return " \"$opt\" needs one of those arguments: on, off !";} - - $hash->{HELPER}{HOMEMODE} = $prop; - setHomeMode($hash); - - } elsif ($opt eq "autocreateCams" && !IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - - camAutocreate($hash); - - } elsif ($opt eq "goPreset" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - if (!$prop) {return "Function \"goPreset\" needs a \"Presetname\" as an argument";} - - $hash->{HELPER}{GOPRESETNAME} = $prop; - $hash->{HELPER}{PTZACTION} = "gopreset"; - doPtzAaction($hash); - - } elsif ($opt eq "optimizeParams" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - my %cpcl = (ntp => 1, mirror => 2, flip => 4, rotate => 8); - extoptpar($hash,$prop,\%cpcl) if($prop); - extoptpar($hash,$prop1,\%cpcl) if($prop1); - extoptpar($hash,$prop2,\%cpcl) if($prop2); - setOptParams($hash); - - } elsif ($opt eq "pirSensor" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - if(ReadingsVal("$name", "CapPIR", "false") eq "false") {return "Function \"$opt\" not possible. Camera \"$name\" don't have a PIR sensor."} - if(!$prop) {return "Function \"$opt\" needs an argument";} - $hash->{HELPER}{PIRACT} = ($prop eq "activate")?0:($prop eq "deactivate")?-1:5; - if($hash->{HELPER}{PIRACT} == 5) {return " Illegal argument for \"$opt\" detected, use \"activate\" or \"activate\" !";} - managePir($hash); - - } elsif ($opt eq "runPatrol" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - if (!$prop) {return "Function \"$opt\" needs a \"Patrolname\" as an argument";} - - $hash->{HELPER}{GOPATROLNAME} = $prop; - $hash->{HELPER}{PTZACTION} = "runpatrol"; - doPtzAaction($hash); - - } elsif ($opt eq "goAbsPTZ" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - if ($prop eq "up" || $prop eq "down" || $prop eq "left" || $prop eq "right") { - if ($prop eq "up") {$hash->{HELPER}{GOPTZPOSX} = 320; $hash->{HELPER}{GOPTZPOSY} = 480;} - if ($prop eq "down") {$hash->{HELPER}{GOPTZPOSX} = 320; $hash->{HELPER}{GOPTZPOSY} = 0;} - if ($prop eq "left") {$hash->{HELPER}{GOPTZPOSX} = 0; $hash->{HELPER}{GOPTZPOSY} = 240;} - if ($prop eq "right") {$hash->{HELPER}{GOPTZPOSX} = 640; $hash->{HELPER}{GOPTZPOSY} = 240;} - - $hash->{HELPER}{PTZACTION} = "goabsptz"; - doPtzAaction($hash); - return; - - } else { - if ($prop !~ /\d+/x || $prop1 !~ /\d+/x || abs($prop) > 640 || abs($prop1) > 480) { - return "Function \"goAbsPTZ\" needs two coordinates, posX=0-640 and posY=0-480, as arguments or use up, down, left, right instead"; - } - - $hash->{HELPER}{GOPTZPOSX} = abs($prop); - $hash->{HELPER}{GOPTZPOSY} = abs($prop1); - - $hash->{HELPER}{PTZACTION} = "goabsptz"; - doPtzAaction($hash); - - return; - } - return "Function \"goAbsPTZ\" needs two coordinates, posX=0-640 and posY=0-480, as arguments or use up, down, left, right instead"; - - } elsif ($opt eq "move" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - return "PTZ version of Synology API isn't set. Use \"get $name scanVirgin\" first." if(!$hash->{HELPER}{APIPTZMAXVER}); - - if($hash->{HELPER}{APIPTZMAXVER} <= 4) { - if (!defined($prop) || ($prop !~ /^up$|^down$|^left$|^right$|^dir_\d$/x)) {return "Function \"move\" needs an argument like up, down, left, right or dir_X (X = 0 to CapPTZDirections-1)";} - $hash->{HELPER}{GOMOVEDIR} = $prop; - - } elsif ($hash->{HELPER}{APIPTZMAXVER} >= 5) { - if (!defined($prop) || ($prop !~ /^right$|^upright$|^up$|^upleft$|^left$|^downleft$|^down$|^downright$/x)) {return "Function \"move\" needs an argument like right, upright, up, upleft, left, downleft, down, downright ";} - my %dirs = ( - right => 0, - upright => 4, - up => 8, - upleft => 12, - left => 16, - downleft => 20, - down => 24, - downright => 28, - ); - $hash->{HELPER}{GOMOVEDIR} = $dirs{$prop}; - } - - $hash->{HELPER}{GOMOVETIME} = defined($prop1) ? $prop1 : 1; - - $hash->{HELPER}{PTZACTION} = "movestart"; - doPtzAaction($hash); - - } elsif ($opt eq "runView" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - - my $spec = join(" ",@a); - if($spec =~ /STRM:/x) { - ($hash->{HELPER}{INFORM}) = $spec =~ m/STRM:(.*)/xi; # Call by SSCamSTRM-Device - } - - if ($prop eq "live_open") { - if ($prop1) {$hash->{HELPER}{VIEWOPENROOM} = $prop1;} else {delete $hash->{HELPER}{VIEWOPENROOM};} - $hash->{HELPER}{OPENWINDOW} = 1; - $hash->{HELPER}{WLTYPE} = "link"; - $hash->{HELPER}{ALIAS} = "LiveView"; - $hash->{HELPER}{RUNVIEW} = "live_open"; - $hash->{HELPER}{ACTSTRM} = ""; # sprechender Name des laufenden Streamtyps für SSCamSTRM - } elsif ($prop eq "live_link") { - $hash->{HELPER}{OPENWINDOW} = 0; - $hash->{HELPER}{WLTYPE} = "link"; - $hash->{HELPER}{ALIAS} = "LiveView"; - $hash->{HELPER}{RUNVIEW} = "live_link"; - $hash->{HELPER}{ACTSTRM} = ""; # sprechender Name des laufenden Streamtyps für SSCamSTRM - } elsif ($prop eq "lastrec_open") { - if ($prop1) {$hash->{HELPER}{VIEWOPENROOM} = $prop1;} else {delete $hash->{HELPER}{VIEWOPENROOM};} - $hash->{HELPER}{OPENWINDOW} = 1; - $hash->{HELPER}{WLTYPE} = "link"; - $hash->{HELPER}{ALIAS} = "LastRecording"; - $hash->{HELPER}{RUNVIEW} = "lastrec_open"; - $hash->{HELPER}{ACTSTRM} = ""; # sprechender Name des laufenden Streamtyps für SSCamSTRM - } elsif ($prop eq "lastrec_fw") { # Video in iFrame eingebettet - $hash->{HELPER}{OPENWINDOW} = 0; - $hash->{HELPER}{WLTYPE} = "iframe"; - $hash->{HELPER}{ALIAS} = " "; - $hash->{HELPER}{RUNVIEW} = "lastrec"; - $hash->{HELPER}{ACTSTRM} = "last Recording"; # sprechender Name des laufenden Streamtyps für SSCamSTRM - } elsif ($prop eq "lastrec_fw_MJPEG") { # “video/avi” – MJPEG format event - $hash->{HELPER}{OPENWINDOW} = 0; - $hash->{HELPER}{WLTYPE} = "image"; - $hash->{HELPER}{ALIAS} = " "; - $hash->{HELPER}{RUNVIEW} = "lastrec"; - $hash->{HELPER}{ACTSTRM} = "last Recording"; # sprechender Name des laufenden Streamtyps für SSCamSTRM - } elsif ($prop eq "lastrec_fw_MPEG4/H.264") { # “video/mp4” – MPEG4/H.264 format event - $hash->{HELPER}{OPENWINDOW} = 0; - $hash->{HELPER}{WLTYPE} = "video"; - $hash->{HELPER}{ALIAS} = " "; - $hash->{HELPER}{RUNVIEW} = "lastrec"; - $hash->{HELPER}{ACTSTRM} = "last Recording"; # sprechender Name des laufenden Streamtyps für SSCamSTRM - } elsif ($prop eq "live_fw") { - $hash->{HELPER}{OPENWINDOW} = 0; - $hash->{HELPER}{WLTYPE} = "image"; - $hash->{HELPER}{ALIAS} = " "; - $hash->{HELPER}{RUNVIEW} = "live_fw"; - $hash->{HELPER}{ACTSTRM} = "MJPEG Livestream"; # sprechender Name des laufenden Streamtyps für SSCamSTRM - } elsif ($prop eq "live_fw_hls") { - return "API \"SYNO.SurveillanceStation.VideoStream\" is not available or Reading \"CamStreamFormat\" is not \"HLS\". May be your API version is 2.8 or higher." if(!IsCapHLS($hash)); - $hash->{HELPER}{OPENWINDOW} = 0; - $hash->{HELPER}{WLTYPE} = "hls"; - $hash->{HELPER}{ALIAS} = "View only on compatible browsers"; - $hash->{HELPER}{RUNVIEW} = "live_fw_hls"; - $hash->{HELPER}{ACTSTRM} = "HLS Livestream"; # sprechender Name des laufenden Streamtyps für SSCamSTRM - } elsif ($prop eq "lastsnap_fw") { - $hash->{HELPER}{LSNAPBYSTRMDEV} = 1 if($prop1); # Anzeige durch SSCamSTRM-Device ausgelöst - $hash->{HELPER}{LSNAPBYDEV} = 1 if(!$prop1); # Anzeige durch SSCam ausgelöst - $hash->{HELPER}{OPENWINDOW} = 0; - $hash->{HELPER}{WLTYPE} = "base64img"; - $hash->{HELPER}{ALIAS} = " "; - $hash->{HELPER}{RUNVIEW} = "lastsnap_fw"; - $hash->{HELPER}{ACTSTRM} = "last Snapshot"; # sprechender Name des laufenden Streamtyps für SSCamSTRM - } else { - return "$prop isn't a valid option of runview, use one of live_fw, live_link, live_open, lastrec_fw, lastrec_open, lastsnap_fw"; - } - runLiveview($hash); - - } elsif ($opt eq "hlsreactivate" && IsModelCam($hash)) { - # ohne SET-Menüeintrag - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - reactivateHls($hash); - - } elsif ($opt eq "hlsactivate" && IsModelCam($hash)) { - # ohne SET-Menüeintrag - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - - my $spec = join(" ",@a); - if($spec =~ /STRM:/x) { - ($hash->{HELPER}{INFORM}) = $spec =~ m/STRM:(.*)/xi; # Call by SSCamSTRM-Device - } - activateHls($hash); - - } elsif ($opt eq "refresh" && IsModelCam($hash)) { - # ohne SET-Menüeintrag - my $spec = join(" ",@a); - if($spec =~ /STRM:/x) { - ($hash->{HELPER}{INFORM}) = $spec =~ m/STRM:(.*)/xi; # Refresh by SSCamSTRM-Device - roomRefresh($hash,0,0,1); # kein Room-Refresh, kein SSCam-state-Event, SSCamSTRM-Event - } - - } elsif ($opt eq "extevent" && !IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - $hash->{HELPER}{EVENTID} = $prop; - extEvent($hash); - - } elsif ($opt eq "stopView" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - - my $spec = join(" ",@a); - if($spec =~ /STRM:/x) { - ($hash->{HELPER}{INFORM}) = $spec =~ m/STRM:(.*)/xi; # Stop by SSCamSTRM-Device - } - stopLiveview($hash); - - } elsif ($opt eq "setPreset" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - if (!$prop) {return qq{Syntax of function "$opt" was wrong. Please use "set $name setPreset []"};} - $hash->{HELPER}{PNUMBER} = $prop; - $hash->{HELPER}{PNAME} = $prop1?$prop1:$prop; # wenn keine Presetname angegeben -> Presetnummer als Name verwenden - $hash->{HELPER}{PSPEED} = $prop2 if($prop2); - setPreset($hash); - - } elsif ($opt eq "setHome" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - if (!$prop) {return "Function \"$opt\" needs a \"Presetname\" as argument";} - $hash->{HELPER}{SETHOME} = $prop; - setHome($hash); - - } elsif ($opt eq "delPreset" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - if (!$prop) {return qq{Function "$opt" needs a "Presetname" as argument};} - $hash->{HELPER}{DELPRESETNAME} = $prop; - delPreset($hash); - - } else { - return "$setlist"; - } - -return; -} - -################################################################ -sub Get { - my ($hash, @a) = @_; - return "\"get X\" needs at least an argument" if ( @a < 2 ); - my $name = shift @a; - my $opt = shift @a; - my $arg = shift @a; - my $arg1 = shift @a; - my $arg2 = shift @a; - my $ret = ""; - my $getlist; - - if(!$hash->{CREDENTIALS}) { - return; - - } elsif(IsModelCam($hash)) { - # getlist für Cams - $getlist = "Unknown argument $opt, choose one of ". - "caminfoall:noArg ". - "caminfo:noArg ". - ((AttrVal($name,"snapGalleryNumber",undef) || AttrVal($name,"snapGalleryBoost",0)) - ? "snapGallery:noArg " : "snapGallery:$defSnum "). - (IsCapPTZPan($hash) ? "listPresets:noArg " : ""). - "snapinfo:noArg ". - "svsinfo:noArg ". - "saveRecording ". - "snapfileinfo:noArg ". - "eventlist:noArg ". - "stmUrlPath:noArg ". - "storedCredentials:noArg ". - "scanVirgin:noArg ". - "versionNotes " - ; - } else { - # getlist für SVS Devices - $getlist = "Unknown argument $opt, choose one of ". - "caminfoall:noArg ". - ($hash->{HELPER}{APIHMMAXVER}?"homeModeState:noArg ": ""). - "svsinfo:noArg ". - "listLog ". - "storedCredentials:noArg ". - "scanVirgin:noArg ". - "versionNotes " - ; - } - - return if(IsDisabled($name)); - - if ($opt eq "caminfo") { - # "1" ist Statusbit für manuelle Abfrage, kein Einstieg in Pollingroutine - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - getCamInfo($hash); - - } elsif ($opt eq "caminfoall") { - # "1" ist Statusbit für manuelle Abfrage, kein Einstieg in Pollingroutine - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - getCaminfoAll($hash,1); - - } elsif ($opt eq "homeModeState" && !IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - getHomeModeState($hash); - - } elsif ($opt eq "listLog" && !IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - # übergebenen CL-Hash (FHEMWEB) in Helper eintragen - getClHash($hash,1); - - extlogargs($hash, $arg) if($arg); - extlogargs($hash, $arg1) if($arg1); - extlogargs($hash, $arg2) if($arg2); - getSvsLog ($hash); - - } elsif ($opt eq "listPresets" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - # übergebenen CL-Hash (FHEMWEB) in Helper eintragen - getClHash($hash,1); - getPresets($hash); - - } elsif ($opt eq "saveRecording") { - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - $hash->{HELPER}{RECSAVEPATH} = $arg if($arg); - getRecAndSave($hash); - - } elsif ($opt eq "svsinfo") { - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - getSvsInfo($hash); - - } elsif ($opt eq "storedCredentials") { - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - # Credentials abrufen - my ($success, $username, $password) = getCredentials($hash,0,"svs"); - unless ($success) {return "Credentials couldn't be retrieved successfully - see logfile"}; - - my ($smtpsuccess, $smtpuname, $smtpword) = getCredentials($hash,0,"smtp"); - my $so; - if($smtpsuccess) { - $so = "SMTP-Username: $smtpuname, SMTP-Password: $smtpword"; - } else { - $so = "SMTP credentials are not set"; - } - return "Stored Credentials to access surveillance station or DSM:\n". - "=========================================================\n". - "Username: $username, Password: $password\n". - "\n". - "Stored Credentials to access SMTP server:\n". - "=========================================\n". - "$so\n"; - - } elsif ($opt eq "snapGallery" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return qq{Credentials of $name are not set - make sure you've set it with "set $name credentials username password"};} - my $txt = getClHash($hash); - return $txt if($txt); - - if(!AttrVal($name, "snapGalleryBoost",0)) { - # Snaphash ist nicht vorhanden und wird abgerufen - $hash->{HELPER}{GETSNAPGALLERY} = 1; - - # snap-Infos für Gallerie abrufen - my ($sg,$slim,$ssize); - $slim = $arg // AttrVal($name,"snapGalleryNumber",$defSlim); # Anzahl der abzurufenden Snapshots - $ssize = (AttrVal($name,"snapGallerySize","Icon") eq "Icon") ? 1 : 2; # Image Size 1-Icon, 2-Full - - getSnapInfo("$name:$slim:$ssize"); - - } else { - # Snaphash ist vorhanden und wird zur Ausgabe aufbereitet - $hash->{HELPER}{SNAPLIMIT} = AttrVal($name,"snapGalleryNumber",$defSlim); - my %pars = ( linkparent => $name, - linkname => '', - ftui => 0 - ); - my $htmlCode = composeGallery(\%pars); - for (my $k=1; (defined($hash->{HELPER}{CL}{$k})); $k++ ) { - if ($hash->{HELPER}{CL}{$k}->{COMP}) { - # CL zusammengestellt (Auslösung durch Notify) - asyncOutput($hash->{HELPER}{CL}{$k}, "$htmlCode"); - } else { - # Output wurde über FHEMWEB ausgelöst - return $htmlCode; - } - } - delete($hash->{HELPER}{CL}); - } - - } elsif ($opt eq "snapinfo" && IsModelCam($hash)) { - # Schnappschußgalerie abrufen oder nur Info des letzten Snaps - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - my ($slim,$ssize) = snapLimSize($hash,1); # Force-Bit, es wird $hash->{HELPER}{GETSNAPGALLERY} gesetzt ! - getSnapInfo("$name:$slim:$ssize"); - - } elsif ($opt eq "snapfileinfo" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - if (!ReadingsVal("$name", "LastSnapId", undef)) {return "Reading LastSnapId is empty - please take a snapshot before !"} - getSnapFilename($hash); - - } elsif ($opt eq "eventlist" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - getEventList ($hash); - - } elsif ($opt eq "stmUrlPath" && IsModelCam($hash)) { - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - getStmUrlPath ($hash); - - } elsif ($opt eq "scanVirgin") { - if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - sessionOff($hash); - delete $hash->{HELPER}{APIPARSET}; - delete $hash->{CAMID}; - # alte Readings außer state löschen - my @allrds = keys%{$defs{$name}{READINGS}}; - for my $key(@allrds) { - # Log3 ($name, 1, "DbRep $name - Reading Schlüssel: $key"); - delete($defs{$name}{READINGS}{$key}) if($key ne "state"); - } - # "1" ist Statusbit für manuelle Abfrage, kein Einstieg in Pollingroutine - getCaminfoAll($hash,1); - - } elsif ($opt =~ /versionNotes/x) { - my $header = "Module release information
"; - my $header1 = "Helpful hints
"; - my %hs; - - # Ausgabetabelle erstellen - my ($val0,$val1); - my $i = 0; - - $ret = ""; - - # Hints - if(!$arg || $arg =~ /hints/x || $arg =~ /[\d]+/x) { - $ret .= sprintf("
$header1
"); - $ret .= ""; - $ret .= ""; - $ret .= ""; - if($arg && $arg =~ /[\d]+/x) { - my @hints = split(",",$arg); - for (@hints) { - if(AttrVal("global","language","EN") eq "DE") { - $hs{$_} = $vHintsExt_de{$_}; - } else { - $hs{$_} = $vHintsExt_en{$_}; - } - } - } else { - if(AttrVal("global","language","EN") eq "DE") { - %hs = %vHintsExt_de; - } else { - %hs = %vHintsExt_en; - } - } - $i = 0; - for my $key (sortVersion("desc",keys %hs)) { - $val0 = $hs{$key}; - $ret .= sprintf("" ); - $ret .= ""; - $i++; - if ($i & 1) { - # $i ist ungerade - $ret .= ""; - } else { - $ret .= ""; - } - } - $ret .= ""; - $ret .= ""; - $ret .= "
$key $val0
"; - $ret .= "
"; - } - - # Notes - if(!$arg || $arg =~ /rel/x) { - $ret .= sprintf("
$header
"); - $ret .= ""; - $ret .= ""; - $ret .= ""; - $i = 0; - for my $key (sortVersion("desc",keys %vNotesExtern)) { - ($val0,$val1) = split(/\s/x,$vNotesExtern{$key},2); - $ret .= sprintf("" ); - $ret .= ""; - $i++; - if ($i & 1) { - # $i ist ungerade - $ret .= ""; - } else { - $ret .= ""; - } - } - $ret .= ""; - $ret .= ""; - $ret .= "
$key $val0 $val1
"; - $ret .= "
"; - } - - $ret .= ""; - - return $ret; - - } else { - return "$getlist"; - } - -return $ret; # do not generate trigger out of command -} - ###################################################################################### # Kamera Liveview Anzeige in FHEMWEB ###################################################################################### @@ -2143,9 +1033,8 @@ sub FWsummaryFn { my $attr = AttrVal($d, "htmlattr", " "); Log3($name, 4, "$name - FWsummaryFn called - FW_wname: $FW_wname, device: $d, room: $room, attributes: $attr"); - # Javascript Bibliothek für Tooltips (http://www.walterzorn.de/tooltip/tooltip.htm#download) und Texte my $calias = $hash->{CAMNAME}; # Alias der Kamera - my $ttjs = "/fhem/pgm2/sscam_tooltip.js"; + my ($ttrefresh, $ttrecstart, $ttrecstop, $ttsnap, $ttcmdstop, $tthlsreact, $ttmjpegrun, $tthlsrun, $ttlrrun, $tth264run, $ttlmjpegrun, $ttlsnaprun); if(AttrVal("global","language","EN") =~ /EN/x) { $ttrecstart = $ttips_en{"ttrecstart"}; $ttrecstart =~ s/§NAME§/$calias/gx; @@ -2153,7 +1042,8 @@ sub FWsummaryFn { $ttsnap = $ttips_en{"ttsnap"}; $ttsnap =~ s/§NAME§/$calias/gx; $ttcmdstop = $ttips_en{"ttcmdstop"}; $ttcmdstop =~ s/§NAME§/$calias/gx; $tthlsreact = $ttips_en{"tthlsreact"}; $tthlsreact =~ s/§NAME§/$calias/gx; - } else { + } + else { $ttrecstart = $ttips_de{"ttrecstart"}; $ttrecstart =~ s/§NAME§/$calias/gx; $ttrecstop = $ttips_de{"ttrecstop"}; $ttrecstop =~ s/§NAME§/$calias/gx; $ttsnap = $ttips_de{"ttsnap"}; $ttsnap =~ s/§NAME§/$calias/gx; @@ -2161,65 +1051,69 @@ sub FWsummaryFn { $tthlsreact = $ttips_de{"tthlsreact"}; $tthlsreact =~ s/§NAME§/$calias/gx; } - $ret .= ""; - if($wltype eq "image") { - if(ReadingsVal($name, "SVSversion", "8.2.3-5828") eq "8.2.3-5828" && ReadingsVal($name, "CamVideoType", "") !~ /MJPEG/x) { + if(ReadingsVal($name, "SVSversion", "") eq "8.2.3-5828" && ReadingsVal($name, "CamVideoType", "") !~ /MJPEG/x) { $ret .= "
Because SVS version 8.2.3-5828 is running you cannot see the MJPEG-Stream. Please upgrade to a higher SVS version !

"; - } else { + } + else { $ret .= "
"; } - $ret .= "$imgstop "; - $ret .= $imgblank; + + $ret .= "$imgstop "; + $ret .= $imgblank; + if($hash->{HELPER}{RUNVIEW} =~ /live_fw/x) { if(ReadingsVal($d, "Record", "Stop") eq "Stop") { # Aufnahmebutton endlos Start - $ret .= "$imgrecendless "; - } else { + $ret .= "$imgrecendless "; + } + else { # Aufnahmebutton Stop - $ret .= "$imgrecstop "; + $ret .= "$imgrecstop "; } - $ret .= "$imgdosnap "; - } + $ret .= "$imgdosnap "; + } + $ret .= "
"; + if($hash->{HELPER}{AUDIOLINK} && ReadingsVal($d, "CamAudioType", "Unknown") !~ /Unknown/x) { $ret .= ""; - } - - } elsif($wltype eq "iframe") { + } + } + elsif($wltype eq "iframe") { $ret .= ""; $ret .= "
"; - $ret .= "$imgstop "; + $ret .= "$imgstop "; if($hash->{HELPER}{AUDIOLINK} && ReadingsVal($d, "CamAudioType", "Unknown") !~ /Unknown/x) { $ret .= ""; - } - - } elsif($wltype eq "embed") { + } + } + elsif($wltype eq "embed") { $ret .= ""; if($hash->{HELPER}{AUDIOLINK} && ReadingsVal($d, "CamAudioType", "Unknown") !~ /Unknown/x) { $ret .= ""; - } - - } elsif($wltype eq "link") { + } + } + elsif($wltype eq "link") { $alias = $hash->{HELPER}{ALIAS}; $ret .= "$alias
"; - - } elsif($wltype eq "base64img") { + } + elsif($wltype eq "base64img") { $alias = $hash->{HELPER}{ALIAS}; $ret .= "$alias
"; - $ret .= "$imgstop "; + $ret .= "$imgstop "; $ret .= $imgblank; - $ret .= "$imgdosnap "; - - } elsif($wltype eq "hls") { + $ret .= "$imgdosnap "; + } + elsif($wltype eq "hls") { $alias = $hash->{HELPER}{ALIAS}; $ret .= ""; $ret .= "
"; - $ret .= "$imgstop "; - $ret .= "$imghlsreact "; + $ret .= "$imgstop "; + $ret .= "$imghlsreact "; $ret .= $imgblank; - if(ReadingsVal($d, "Record", "Stop") eq "Stop") { - # Aufnahmebutton endlos Start - $ret .= "$imgrecendless "; - } else { - # Aufnahmebutton Stop - $ret .= "$imgrecstop "; + if(ReadingsVal($d, "Record", "Stop") eq "Stop") { # Aufnahmebutton endlos Start + $ret .= "$imgrecendless "; + } + else { # Aufnahmebutton Stop + $ret .= "$imgrecstop "; } - $ret .= "$imgdosnap "; + $ret .= "$imgdosnap "; - } elsif($wltype eq "video") { + } + elsif($wltype eq "video") { $ret .= ""; $ret .= "
"; - $ret .= "$imgstop "; + $ret .= "$imgstop "; $ret .= "
"; if($hash->{HELPER}{AUDIOLINK} && ReadingsVal($d, "CamAudioType", "Unknown") !~ /Unknown/x) { $ret .= "