From 179084a4bbf86f132fc901c9648463479de48c35 Mon Sep 17 00:00:00 2001 From: delmar <> Date: Sun, 2 Feb 2020 20:56:54 +0000 Subject: [PATCH] 70_DENON_AVR: initial commit. git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@21096 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- FHEM/70_DENON_AVR.pm | 4306 +++++++++++++++++++++++++++++++++++++ FHEM/71_DENON_AVR_ZONE.pm | 1441 +++++++++++++ 2 files changed, 5747 insertions(+) create mode 100755 FHEM/70_DENON_AVR.pm create mode 100644 FHEM/71_DENON_AVR_ZONE.pm diff --git a/FHEM/70_DENON_AVR.pm b/FHEM/70_DENON_AVR.pm new file mode 100755 index 000000000..7a6a9d4d1 --- /dev/null +++ b/FHEM/70_DENON_AVR.pm @@ -0,0 +1,4306 @@ +############################################################################### +# 70_DENON_AVR +# +# This file is part of Fhem. +# +# Fhem is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Fhem is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Fhem. If not, see . +# +############################################################################### +# +# DENON_AVR maintained by Martin Gutenbrunner +# original credits to raman and the community (see Forum thread) +# +# This module enables FHEM to interact with Denon and Marantz audio devices. +# +# Discussed in FHEM Forum: https://forum.fhem.de/index.php/topic,58452.300.html +# +# $Id$ + +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); + +sub DENON_AVR_Get($@); +sub DENON_AVR_Set($@); +sub DENON_AVR_Define($$); +sub DENON_AVR_Undefine($$); +sub DENON_AVR_Notify($$); + + +# Database and call-functions +###################################################################### +my $DENON_db = { + 'SSLEV' => { + 'FL' => 'Front-Left', + 'FR' => 'Front-Right', + 'C' => 'Center', + 'SW' => 'Subwoofer', + 'SW2' => 'Subwoofer2', + 'SL' => 'Surround-Left', + 'SR' => 'Surround-Right', + 'SBL' => 'Surround-Back-Left', + 'SBR' => 'Surround-Back-Right', + 'SB' => 'Surround-Back', + 'FHL' => 'Front-Height-Left', + 'FHR' => 'Front-Height-Right', + 'FWL' => 'Front-Wide-Left', + 'FWR' => 'Front-Wide-Right', + 'TFL' => 'Top-Front-Left', + 'TFR' => 'Top-Front-Right', + 'TML' => 'Top-Middle-Left', + 'TMR' => 'Top-Middle-Right', + 'TRL' => 'Top-Rear-Left', + 'TRR' => 'Top-Rear-Right', + 'RHL' => 'Rear-Height-Left', + 'RHR' => 'Rear-Height-Right', + 'FDL' => 'Front-Dolby-Left', + 'FDR' => 'Front-Dolby-Right', + 'SDL' => 'Surround-Dolby-Left', + 'SDR' => 'Surround,Dolby-Right', + 'BDL' => 'Back-Dolby-Left', + 'BDR' => 'Back-Dolby-Right', + 'SHL' => 'Surround-Height-Left', + 'SHR' => 'Surround-Hight-Right', + 'TS' => 'Top-Surround', + }, + 'CV' => { + 'FL' => 'FrontLeft', + 'FR' => 'FrontRight', + 'C' => 'Center', + 'SW' => 'Subwoofer', + 'SW2' => 'Subwoofer2', + 'SL' => 'SourroundLeft', + 'SR' => 'SourroundRight', + 'SBL' => 'SourroundBackLeft', + 'SBR' => 'SourroundBackRight', + 'SB' => 'SourroundBack', + 'FHL' => 'FrontHeightLeft', + 'FHR' => 'FrontHeightRight', + 'FWL' => 'FrontWideLeft', + 'FWR' => 'FrontWideRight', + 'TFL' => 'TopFrontLeft', + 'TFR' => 'TopFrontRight', + 'TML' => 'TopMiddleLeft', + 'TMR' => 'TopMiddleRight', + 'TRL' => 'TopRearLeft', + 'TRR' => 'TopRearRight', + 'RHL' => 'RearHeightLeft', + 'RHR' => 'RearHeightRight', + 'FDL' => 'FrontDolbyLeft', + 'FDR' => 'FrontDolbyRight', + 'SDL' => 'SurroundDolbyLeft', + 'SDR' => 'SurroundDolbRight', + 'BDL' => 'BackDolbyLeft', + 'BDR' => 'BackDolbyRight', + 'SHL' => 'SurroundHeightLeft', + 'SHR' => 'SurroundHightRight', + 'TS' => 'TopSurround', + }, + 'DC' => { + 'AUTO' => 'auto', + 'PCM' => 'PCM', + 'DTS' => 'DTS', + }, + 'DIM' => { #Dim-States + 'bright' => 'BRI', + 'dim' => 'DIM', + 'dark' => 'DAR', + 'off' => 'OFF', + 'toggle' => 'SEL', + }, + 'ECO' => { #ECO-Mode + 'on' => 'ON', + 'auto' => 'AUTO', + 'off' => 'OFF', + }, + 'MN' => { #System-Remote: + 'up' => 'CUP', + 'down' => 'CDN', + 'left' => 'CLT', + 'right' => 'CRT', + 'enter' => 'ENT', + 'return' => 'RTN', + 'option' => 'OPT', + 'info' => 'INF', + 'channelLevelAdjust' => 'CHL', + 'info' => 'INF', + 'allZoneStereo' => 'ZST', + 'info' => 'INF', + }, + 'MS' => { #Surround-Mode + 'Movie' => 'MOVIE', + 'Music' => 'MUSIC', + 'Game' => 'GAME', + 'Direct' => 'DIRECT', + 'Pure_Direct' => 'PURE DIRECT', + 'Stereo' => 'STEREO', + 'Auto' => 'AUTO', + 'Dolby_Digital' => 'DOLBY DIGITAL', + 'DTS_Surround' => 'DTS SURROUND', + 'Auro3D' => 'AURO3D', + 'Auro2D_Surround' => 'AURO2DSURR', + 'Multichannel_Stereo' => 'MCH STEREO', + 'Wide_Screen' => 'WIDE SCREEN', + 'Super_Stadium' => 'SUPER STADIUM', + 'Rock_Arena' => 'ROCK ARENA', + 'Jazz_Club' => 'JAZZ CLUB', + 'Classic_Concert' => 'CLASSIC CONCERT', + 'Mono_Movie' => 'MONO MOVIE', + 'Matrix' => 'MATRIX', + 'Video_Game' => 'VIDEO GAME', + 'Virtual' => 'VIRTUAL', + 'Left' => 'LEFT', + 'Right' => 'RIGHT', + 'Quick1' => 'QUICK1', + 'Quick2' => 'QUICK2', + 'Quick3' => 'QUICK3', + 'Quick4' => 'QUICK4', + 'Quick5' => 'QUICK5', + 'Smart1' => 'SMART1', + 'Smart2' => 'SMART2', + 'Smart3' => 'SMART3', + 'Smart4' => 'SMART4', + 'Smart5' => 'SMART5', + }, + 'MU' => { + 'on' => 'ON', + 'off' => 'OFF', + 'status' => '?', + }, + 'NS' => { + #System-Info: + 'FRN' => 'model', # device-type + #Remote: + 'up' => '90', + 'down' => '91', + 'left' => '92', + 'right' => '93', + 'enter' => '94', + 'play' => '9A', + 'pause' => '9B', + 'stop' => '9C', + 'skipPlus' => '9D', + 'skipMinus' => '9E', + 'manualSearchPlus' => '9F', + 'manualSearchMinus' => '9G', + 'repeatOne' => '9H', + 'repeatAll' => '9I', + 'repeatOff' => '9J', + 'randomOn' => '9K', + 'randomOff' => '9M', + 'toggleSwitch' => '9W', + 'pageNext' => '9X', + 'pagePrevious' => '9Y', + 'manualSearchStop' => '9Z', + 'toggleRepeat' => 'RPT', + 'toggleRandom' => 'RND', + 'addFavoritesFolder' => 'FV MEM', + }, + 'NSE' => { #Info-Display - for example: + '0' => 'currentMedia', # Now Playing iRadio + '1' => 'currentTitle', # Dua Lipa: Hotter than hell + '2a' => 'currentArtist', # Bayern 3 Live oder currentArtist + '2s' => 'currentStation', # Bayern 3 Live + '3' => 'currentBitrate', # 44.1kHz + '4' => 'currentAlbum', + '5' => 'currentPlaytime', # 0:38 100% + '6' => 'ignore', + '7' => 'ignore', + '8' => 'ignore', + }, + 'PS' => { #Sound-Parameter + 'TONE CTRL' => 'toneControl', + 'DRC' => 'dynamicCompression', + 'LFC' => 'audysseyLFC', + 'LFE' => 'lowFrequencyEffects', + 'BAS' => 'bass', + 'TRE' => 'treble', + 'DIL' => 'dialogLevelAdjust', + 'SWL' => 'subwooferLevelAdjust', + 'CINEMA EQ' => 'cinemaEQ', + 'LOM' => 'loudness', + 'PHG' => { + 'PHG' => 'PLIIheightGain', + 'LOW' => 'low', + 'MID' => 'mid', + 'HI' => 'high', + }, + 'MULTEQ' => { + 'MULTEQ' => 'multEQ', + 'AUDYSSEY' => 'reference', + 'BYP.LR' => 'bypassL/R', + 'FLAT' => 'flat', + 'MANUAL' => 'manual', + 'OFF' => 'off', + }, + 'DYNEQ' => 'dynamicEQ', + 'DYNVOL' => { + 'DYNVOL' => 'dynamicVolume', + 'HEV' => 'heavy', + 'MED' => 'medium', + 'LIT' => 'light', + 'OFF' => 'off', + }, + 'GEQ' => 'graphicEQ', + 'PAN' => 'panorama', + 'CES' => 'centerSpread', + 'BAL' => 'balance', + 'SDB' => 'sdb', + 'SDI' => 'sourceDirect', + + }, + 'PV' => { + 'off' => 'OFF', + 'Standard' => 'STD', + 'Movie' => 'MOV', + 'Vivid' => 'VVD', + 'Stream' => 'STM', + 'Costom' => 'CTM', + 'Day' => 'DAY', + 'Night' => 'NGT', + }, + 'PW' => { + 'on' => 'ON', + 'off' => 'STANDBY', + 'standby' => 'STANDBY', + 'status' => '?', + }, + 'R' => { #default names: + '1' => 'MAIN ZONE', # MAIN ZONE + '2' => 'ZONE2', # ZONE2 + '3' => 'ZONE3', # ZONE3 + '4' => 'ZONE4', # ZONE4 + 'Z' => 'ZONE', # ZONE + }, + 'REMOTE' => { #Remote - all commands: + 'up' => 'CUP', + 'down' => 'CDN', + 'left' => 'CLT', + 'right' => 'CRT', + 'enter' => 'ENT', + 'return' => 'RTN', + 'option' => 'OPT', + 'info' => 'INF', + 'channelLevelAdjust' => 'CHL', + 'info' => 'INF', + 'setup' => '', + 'eco' => '', + 'play' => '9A', + 'pause' => '9B', + 'stop' => '9C', + 'skipPlus' => '9D', + 'skipMinus' => '9E', + 'manualSearchPlus' => '9F', + 'manualSearchMinus' => '9G', + 'repeatOne' => '9H', + 'repeatAll' => '9I', + 'repeatOff' => '9J', + 'randomOn' => '9K', + 'randomOff' => '9M', + 'toggleSwitch' => '9W', + 'pageNext' => '9X', + 'pagePrevious' => '9Y', + 'manualSearchStop' => '9Z', + 'toggleRepeat' => 'RPT', + 'toggleRandom' => 'RND', + 'addFavoritesFolder' => 'FV MEM', + }, + 'SI' => { #Inputs + 'Phono' => 'PHONO', + 'CD' => 'CD', + 'Tuner' => 'TUNER', + 'Dock' => 'DOCK', + 'DVD' => 'DVD', + 'DVR' => 'DVR', + 'Blu-Ray' => 'BD', + 'TV' => 'TV', + 'Sat/Cbl' => 'SAT/CBL', + 'Sat' => 'SAT', + 'Mediaplayer' => 'MPLAY', + 'Game' => 'GAME', + 'HDRadio' => 'HDRADIO', + 'OnlineMusic' => 'NET', + 'Spotify' => 'SPOTIFY', + 'LastFM' => 'LASTFM', + 'Flickr' => 'FLICKR', + 'iRadio' => 'IRADIO', + 'Server' => 'SERVER', + 'Favorites' => 'FAVORITES', + 'Pandora' => 'PANDORA', + 'SiriusXM' => 'SIRIUSXM', + 'Aux1' => 'AUX1', + 'Aux2' => 'AUX2', + 'Aux3' => 'AUX3', + 'Aux4' => 'AUX4', + 'Aux5' => 'AUX5', + 'Aux6' => 'AUX6', + 'Aux7' => 'AUX7', + 'AuxA' => 'AUXA', + 'AuxB' => 'AUXB', + 'AuxC' => 'AUXC', + 'AuxD' => 'AUXD', + 'V.Aux' => 'V.AUX', + 'Bluetooth' => 'BT', + 'Net/Usb' => 'NET/USB', + 'Usb/iPod' => 'USB/IPOD', + 'Usb_play' => 'USB', + 'iPod_play' => 'IPD', + 'iRadio_play' => 'IRP', + 'Favorites_play' => 'FVP', + 'Source' => 'SOURCE', + }, + 'SS' => { #System-Info: + 'FUN' => { # default names: + 'DVD' => '', # DVD + 'DVR' => '', + 'DOCK' => '', + 'BD' => '', # Blu-ray + 'TV' => '', # TV Audio + 'SAT/CBL' => '', # SAT/CBL + 'SAT' => '', # SAT - some old Marantz models + 'MPLAY' => '', # Media Player + 'BT' => '', # Bluetooth + 'GAME' => '', # Game + 'AUX1' => '', # AUX1-7 + 'AUX2' => '', + 'AUX3' => '', + 'AUX4' => '', + 'AUX5' => '', + 'AUX6' => '', + 'AUX7' => '', + 'AUXA' => '', + 'AUXB' => '', + 'AUXC' => '', + 'AUX' => '', + 'V.AUX' => '', + 'CD' => '', # CD + 'PHONO' => '', # Phono + 'HDRADIO' => '', + 'TUNER' => '', + 'FAVORITES' => '', + 'IRADIO' => '', + 'SIRIUSXM' => '', + 'PANDORA' => '', + 'SERVER' => '', + 'FLICKR' => '', + 'NET' => '', + 'LASTFM' => '', + 'NET/USB'=> '', + 'USB/IPOD' => '', + 'USB' => '', + 'IPD' => '', + 'IRP' => '', + 'FVP' => '', + 'SOURCE' => '', + }, + 'LAN' => 'lan', # DEU + 'LOC' => 'lock', # off/on + 'PAA' => { + 'MOD' => { + 'FRB' => '5.1-Channel+FrontB', + 'BIA' => '5.1-Channel (Bi-Amp)', + 'ZO2' => '5.1-Channel+Zone2', + 'ZO3' => '5.1-Channel+Zone3', + 'ZOM' => '5.1-Channel+Zone2/3-Mono', + 'NOR' => '7.1-Kanal', + '2CH' => '7.1/2-Channel-Front', + '91C' => '9.1-Channel', + 'DAT' => 'Dolby Atmos', + }, + }, + 'INF' => { #Informations + 'MO1' => { + 'INT' => 'interface', + 'SUP00' => 'resolution0', + 'SUP01' => 'resolution1', + 'SUP02' => 'resolution2', + 'SUP03' => 'resolution3', + 'SUP04' => 'resolution4', + 'SUP05' => 'resolution5', + }, + 'MO2' => { + 'INT' => 'interface', + 'SUP00' => 'resolution0', + 'SUP01' => 'resolution1', + 'SUP02' => 'resolution2', + 'SUP03' => 'resolution3', + 'SUP04' => 'resolution4', + }, + 'FRM' => 'firmware', + 'AIS' => { + 'FSV' => 'samplingRate', + 'FOR' => 'audioFormat', + 'SIG' => { + '00' => 'na 00', + '01' => 'Analog', + '02' => 'PCM', + '03' => 'Dolby Digital', + '04' => 'Dolby TrueHD', + '05' => 'Dolby Atmos', + '06' => 'DTS', + '07' => 'na 07', + '08' => 'DTS-HD Hi Res', + '09' => 'DTS-HD Mstr', + '10' => 'na 10', + '11' => 'na 11', + '12' => 'Dolby Digital', + '13' => 'PCM Zero', + '14' => 'na 14', + '15' => 'na 15', + '16' => 'na 16', + '17' => 'na 17', + '18' => 'na 18', + '19' => 'na 19', + '20' => 'na 20', + }, + }, + }, + 'SMG' => { #Sound-Mode - status only + 'MUS' => 'Music', + 'MOV' => 'Movie', + 'GAM' => 'Game', + 'PUR' => 'Pure_Direct', + }, + 'SOD' => { # used inputs: USE = aviable / DEL = not aviable + 'DVD' => 'USE', + 'DVR' => 'DEL', + 'DOCK' => 'DEL', + 'BD' => 'USE', + 'TV' => 'USE', + 'SAT/CBL' => 'USE', + 'SAT' => 'DEL', + 'MPLAY' => 'USE', + 'BT' => 'USE', + 'GAME' => 'USE', + 'HDRADIO' => 'DEL', + 'AUX1' => 'USE', + 'AUX2' => 'USE', + 'AUX3' => 'DEL', + 'AUX4' => 'DEL', + 'AUX5' => 'DEL', + 'AUX6' => 'DEL', + 'AUX7' => 'DEL', + 'AUXA' => 'DEL', + 'AUXB' => 'DEL', + 'AUXC' => 'DEL', + 'AUXD' => 'DEL', + 'V.AUX' => 'DEL', + 'CD' => 'USE', + 'PHONO' => 'USE', + 'TUNER' => 'USE', + 'FAVORITES' => 'USE', + 'IRADIO' => 'USE', + 'SIRIUSXM' => 'DEL', + 'PANDORA' => 'DEL', + 'SERVER' => 'USE', + 'FLICKR' => 'USE', + 'NET' => 'USE', + 'NET/USB'=> 'DEL', + 'LASTFM' => 'DEL', + 'USB/IPOD' => 'USE', + 'USB' => 'USE', + 'IPD' => 'USE', + 'IRP' => 'USE', + 'FVP' => 'USE', + 'SOURCE' => 'DEL', + }, + }, + 'SLP' => { #sleep-Mode + '10min' => '010', + '15min' => '015', + '30min' => '030', + '40min' => '040', + '50min' => '050', + '60min' => '060', + '70min' => '070', + '80min' => '080', + '90min' => '090', + '100min' => '100', + '110min' => '110', + '120min' => '120', + 'off' => 'OFF', + }, + 'STBY' => { #autoStandby-Mode + '15min' => '15M', + '30min' => '30M', + '60min' => '60M', + 'off' => 'OFF', + }, + 'SV' => { #Video-Select + 'DVD' => 'DVD', + 'BD' => 'Blu-Ray', + 'TV' => 'TV', + 'SAT/CBL' => 'Sat/Cbl', + 'DVR' => 'DVR', + 'DOCK' => 'Dock', + 'MPLAY' => 'Mediaplayer', + 'GAME' => 'Game', + 'AUX1' => 'Aux1', + 'AUX2' => 'Aux2', + 'AUX3' => 'Aux3', + 'AUX4' => 'Aux4', + 'AUX5' => 'Aux5', + 'AUX6' => 'Aux6', + 'AUX7' => 'Aux7', + 'V.AUX' => 'V.Aux', + 'CD' => 'CD', + 'SOURCE' => 'Source', + 'ON' => 'on', + 'OFF' => 'off', + }, + 'SD' => { #DigitalSound-Select + 'AUTO' => 'auto', + 'HDMI' => 'hdmi', + 'DIGITAL' => 'digital', + 'ANALOG' => 'analog', + 'EXT.IN' => 'externalInput', + '7.1IN' => '7.1input', + 'ARC' => 'ARCplaying', + 'NO' => 'noInput', + }, + 'SWITCH' => { + "on" => "off", + "off" => "on", + "standby" => "on", + }, + 'SY' => { #System-Info + 'MO' => 'model', # AVR-X4100WEUR + 'MODTUN' => 'tuner', # EUR + }, + 'SIGNAL' => { + 'STEREO' => 'PCM', + 'DOLBY' => 'Dolby Digital', + 'DOLBY DIGITAL' => 'Dolby Digital', + 'DOLBY D EX' => 'Dolby Digital EX', + 'DOLBY HD' => 'Dolby TrueHD', + 'DOLBY D+' => 'Dolby Digital Plus', + 'DSD' => 'DSD', + 'MULTI CH IN' => 'PCM Multi', + 'DTS' => 'DTS', + 'DTS HD' => 'DTS-HD', + 'DTS EXPRESS' => 'DTS Express', + 'DTS ES DSCRT6.1' => 'DTS Dscrt 6.1', + 'DTS ES MTRX6.1' => 'DTS Mtrx 6.1', + 'DOLBY ATMOS' => 'Dolby Atmos', + 'AURO3D' => 'Auro-3D', + 'AURO2DSURR' => 'Auro-2D', + }, + 'SOUND' => { + 'STEREO' => 'Stereo', + 'DIRECT' => 'Direct', + 'DSD DIRECT' => 'DSD Direct', + 'PURE DIRECT' => 'Pure Direct', + 'DSD PURE DIRECT' => 'DSD Pure Direct', + 'PURE DIRECT EXT' => 'Pure Direct Ext', + 'MCH STEREO' => 'Multi Ch Stereo', + 'MONO MOVIE' => 'Mono Movie', + 'ROCK ARENA' => 'Rock Arena', + 'JAZZ CLUB' => 'Jazz Club', + 'MATRIX' => 'Matrix', + 'VIRTUAL' => 'Virtual', + 'VIDEO GAME' => 'Video Game', + 'ALL ZONE STEREO' => 'All Zone Stereo', + 'AUDYSSEY DSX' => 'Audyssey DSX', + 'PL DSX' => 'PL DSX', + 'PL2 C DSX' => 'PL2 C DSX', + 'PL2 M DSX' => 'PL2 M DSX', + 'PL2 G DSX' => 'PL2 G DSX', + 'PL2X C DSX' => 'PL2X C DSX', + 'PL2X M DSX' => 'PL2X M DSX', + 'PL2X G DSX' => 'PL2X G DSX', + 'DOLBY PL2 C' => 'Dolby PL2 C', + 'DOLBY PL2 M' => 'Dolby PL2 M', + 'DOLBY PL2 G' => 'Dolby PL2 G', + 'DOLBY PRO LOGIC' => 'Dolby Pro Logic', + 'DOLBY SURROUND' => 'Dolby Surround', + 'DOLBY ATMOS' => 'Dolby Atmos', + 'DOLBY DIGITAL' => 'Dolby Digital', + 'DOLBY PL2 C' => 'Dolby PL2 C', + 'DOLBY PL2 M' => 'Dolby PL2 M', + 'DOLBY PL2 G' => 'Dolby PL2 G', + 'DOLBY PL2X C' => 'Dolby PL2X C', + 'DOLBY PL2X M' => 'Dolby PL2X M', + 'DOLBY PL2X G' => 'Dolby PL2X G', + 'DOLBY PL2Z H' => 'Dolby PL2Z H', + 'DOLBY D EX' => 'Dolby Digital EX', + 'DOLBY D+PL2X C' => 'Dolby Digital+PL2X C', + 'DOLBY D+PL2X M' => 'Dolby Digital+PL2X M', + 'DOLBY D+PL2Z H' => 'Dolby Digital+PL2Z H', + 'DOLBY D+DS' => 'Dolby Digital + Dolby Surround', + 'DOLBY D+NEO:X C' => 'Dolby Digital+Neo:X C', + 'DOLBY D+NEO:X M' => 'Dolby Digital+Neo:X M', + 'DOLBY D+NEO:X G' => 'Dolby Digital+Neo:X G', + 'DOLBY D+NEURAL:X' => 'Dolby Digital + Neural:X', + 'DOLBY D+' => 'Dolby Digital Plus', + 'DOLBY D+ +EX' => 'Dolby Digital Plus+PL2X C', + 'DOLBY D+ +PL2X C' => 'Dolby Digital Plus+PL2X C', + 'DOLBY D+ +PL2X M' => 'Dolby Digital Plus+PL2X M', + 'DOLBY D+ +PL2Z H' => 'Dolby Digital Plus+PL2Z H', + 'DOLBY D+ +PLZ H' => 'Dolby Digital Plus+PLZ H', + 'DOLBY D+ +DS' => 'Dolby Digital+ +DS', + 'DOLBY D+ +NEO:X C' => 'Dolby Digital Plus+Neo:X C', + 'DOLBY D+ +NEO:X M' => 'Dolby Digital Plus+Neo:X M', + 'DOLBY D+ +NEO:X G' => 'Dolby Digital Plus+Neo:X G', + 'DOLBY HD' => 'Dolby TrueHD', + 'DOLBY HD+EX' => 'Dolby HD+EX', + 'DOLBY HD+PL2X C' => 'Dolby HD+PL2X C', + 'DOLBY HD+PL2X M' => 'Dolby HD+PL2X M', + 'DOLBY HD+PL2Z H' => 'Dolby HD+PL2Z H', + 'DOLBY HD+DS' => 'Dolby TrueHD + Dolby Surround', + 'DOLBY HD+NEO:X C' => 'Dolby HD+Neo:X C', + 'DOLBY HD+NEO:X M' => 'Dolby HD+Neo:X M', + 'DOLBY HD+NEO:X G' => 'Dolby HD+Neo:X G', + 'DOLBY HD+NEURAL:X' => 'Dolby TrueHD + Neural:X', + 'DTS SURROUND' => 'DTS Surround', + 'DTS ES DSCRT6.1' => 'DTS ES Dscrt 6.1', + 'DTS ES MTRX6.1' => 'DTS ES Mtrx 6.1', + 'DTS+PL2X C' => 'DTS+PL2X C', + 'DTS+PL2X M' => 'DTS+PL2X M', + 'DTS+PL2Z H' => 'DTS+PL2Z H', + 'DTS+DS' => 'DTS + Dolby Surround', + 'DTS96/24' => 'DTS 96/24', + 'DTS96 ES MTRX' => 'DTS 96 ES MTRX', + 'DTS+NEO:6' => 'DTS+Neo:6', + 'DTS NEO:6 C' => 'DTS Neo:6 C', + 'DTS NEO:X C' => 'DTS Neo:X C', + 'DTS+NEO:X C' => 'DTS+Neo:X C', + 'DTS NEO:6 M' => 'DTS Neo:6 M', + 'DTS NEO:X M' => 'DTS Neo:X M', + 'DTS+NEO:X M' => 'DTS+Neo:X M', + 'DTS+NEO:X G' => 'DTS+Neo:X G', + 'DTS+NEO:X G' => 'DTS+Neo:X G', + 'DTS+NEURAL:X' => 'DTS + Neural:X', + 'DTS HD' => 'DTS-HD', + 'DTS HD TR' => 'DTS-HD TR', + 'DTS HD MSTR' => 'DTS-HD Mstr', + 'DTS HD+PL2X C' => 'DTS-HD+PL2X C', + 'DTS HD+PL2X M' => 'DTS-HD+PL2X M', + 'DTS HD+PL2Z H' => 'DTS-HD+PL2Z H', + 'DTS HD+NEO:6' => 'DTS-HD+Neo:6', + 'DTS HD+DS' => 'DTS-HD + Dolby Surround', + 'DTS HD+NEO:X C' => 'DTS-HD+Neo:X C', + 'DTS HD+NEO:X M' => 'DTS-HD+Neo:X M', + 'DTS HD+NEO:X G' => 'DTS-HD+Neo:X G', + 'DTS HD+NEURAL:X' => 'DTS-HD + Neural:X', + 'DTS EXPRESS' => 'DTS Express', + 'DTS ES 8CH DSCRT' => 'DTS ES 8Ch Dscrt', + 'DTS:X MSTR' => 'DTS:X MSTR', + 'AURO3D' => 'Auro-3D', + 'AURO2DSURR' => 'Auro-2D Surround', + 'MPEG2 AAC' => 'MPEG2 AAC', + 'AAC+DOLBY EX' => 'AAC+Dolby EX', + 'AAC+PL2X C' => 'AAC+PL2X C', + 'AAC+PL2X M' => 'AAC+PL2X M', + 'AAC+PL2Z H' => 'AAC++PL2Z H', + 'AAC+DS' => 'AAC+DS', + 'AAC+NEO:X C' => 'AAC+Neo:X C', + 'AAC+NEO:X M' => 'AAC+Neo:X M', + 'AAC+NEO:X G' => 'AAC+Neo:X G', + 'MULTI CH IN' => 'Multi Ch In', + 'M CH IN+DOLBY EX' => 'Multi Ch In', + 'M CH IN+PL2X C' => 'Multi Ch In+PL2X C', + 'M CH IN+PL2X M' => 'Multi Ch In+PL2X M', + 'M CH IN+PL2Z H' => 'Multi Ch In+PL2Z H', + 'M CH IN+DS' => 'Multi Ch In+DS', + 'MULTI CH IN 7.1' => 'Multi Ch In 7.1', + 'M CH IN+NEO:X C' => 'Multi Ch In+Neo:X C', + 'M CH IN+NEO:X M' => 'Multi Ch In+Neo:X M', + 'M CH IN+NEO:X G' => 'Multi Ch In+Neo:X G', + 'NEO:6 C DSX' => 'Neo:6 C DSX', + 'NEO:6 M DSX' => 'Neo:6 M DSX', + 'NEURAL:X' => 'DTS Neural:X' + }, + 'TF' => { + 'AN' => { + 'up' => 'UP', + 'down' => 'DOWN', + 'status' => '?', + 'RDS' => 'NAME?', + }, + 'HD' => { + 'up' => 'UP', + 'down' => 'DOWN', + 'status' => '?', + }, + }, + 'TP' => { + 'AN' => { + 'up' => 'UP', + 'down' => 'DOWN', + 'status' => '?', + 'memory' => 'MEM', + }, + 'HD' => { + 'up' => 'UP', + 'down' => 'DOWN', + 'status' => '?', + 'memory' => 'MEM', + }, + }, + 'TM' => { + 'AN' => { + 'AM' => 'AM', + 'FM' => 'FM', + 'auto' => 'AUTO', + 'manual' => 'MANUAL', + }, + 'HD' => { + 'AM' => 'AM', + 'FM' => 'FM', + 'status' => '?', + 'autoHD' => 'AUTOHD', + 'auto' => 'AUTO', + 'manual' => 'MANUAL', + 'analogAuto' => 'ANAAUTO', + 'analogManual' => 'ANMANUAL', + }, + }, + 'TOGGLE' => { + "on" => "auto", + "off" => "on", + "auto" => "off", + }, + 'VS' => { + 'ASP' => { + 'ASP' => 'aspectRatio', + 'NRM' => '4:3', + 'FUL' => '16:9', + }, + 'MONI' => { + 'MONI' => 'monitorOut', + 'AUTO' => 'auto', + '1' => '1', + '2' => '2', + }, + 'SC' => { + 'SC' => 'resolution', + 'AUTO' => 'auto', + '48P' => '480p/576p', + '10I' => '1080i', + '48P' => '480p/576p', + '72P' => '720p', + '10P' => '1080p', + '10P24' => '1080p:24Hz', + '4K' => '4K', + '4KF' => '4K(60/50)', + }, + 'SCH' => { + 'SCH' => 'resolutionHDMI', + 'AUTO' => 'auto', + '48P' => '480p/576p', + '10I' => '1080i', + '48P' => '480p/576p', + '72P' => '720p', + '10P' => '1080p', + '10P24' => '1080p:24Hz', + '4K' => '4K', + '4KF' => '4K(60/50)', + }, + 'AUDIO' => { # HDMI AUDIO Output + 'AUDIO' => 'audioOutHDMI', + 'AMP' => 'amplifier', + 'TV' => 'tv', + }, + 'VPM' => { #Video Processing Mode + 'VPM' => 'videoProcessingMode', + 'AUTO' => 'auto', + 'GAME' => 'Game', + 'MOVI' => 'Movie', + }, + 'VST' => 'verticalStretch', + }, +}; + +my $DENON_db_ceol = { + 'MN' => { #System-Remote: + 'up' => 'CUP', + 'down' => 'CDN', + 'left' => 'CLT', + 'right' => 'CRT', + 'enter' => 'ENT', + 'favorite_on' => 'FAV ON', + 'favorite_off' => 'FAV OFF', + }, +}; + +sub +DENON_GetValue($$;$;$;$) { + my ( $status, $command, $inf1, $inf2, $inf3) = @_; + my $name = "Denon"; + + my $info1 = (defined($inf1) ? $inf1 : "na"); + my $info2 = (defined($inf2) ? $inf2 : "na"); + my $info3 = (defined($inf3) ? $inf3 : "na"); + + if ( $info1 eq "na" && $info2 eq "na" && $info3 eq "na" + && defined( $DENON_db->{$status}{$command} ) ) + { + my $value = eval { $DENON_db->{$status}{$command} }; + $value = $@ ? "unknown" : $value; + return $value; + } + elsif ( defined($DENON_db->{$status}{$command}{$info1} ) && $info2 eq "na" && $info3 eq "na" ) { + my $value = eval { $DENON_db->{$status}{$command}{$info1} }; + $value = $@ ? "unknown" : $value; + return $value; + } + elsif ( defined($DENON_db->{$status}{$command}{$info1}{$info2}) && $info3 eq "na" ) { + my $value = eval { $DENON_db->{$status}{$command}{$info1}{$info2} }; + $value = $@ ? "unknown" : $value; + return $value; + } + elsif ( defined($DENON_db->{$status}{$command}{$info1}{$info2}{$info3}) ) { + my $value = eval { $DENON_db->{$status}{$command}{$info1}{$info2}{$info3} }; + $value = $@ ? "unknown" : $value; + return $value; + } + else { + return "unknown"; + } +} + +sub +DENON_SetValue($$$;$;$;$) { + my ( $value, $status, $command, $inf1, $inf2, $inf3) = @_; + + my $info1 = (defined($inf1) ? $inf1 : "na"); + my $info2 = (defined($inf2) ? $inf2 : "na"); + my $info3 = (defined($inf3) ? $inf3 : "na"); + + if ( $info1 eq "na" && $info2 eq "na" && $info3 eq "na" + && defined( $DENON_db->{$status}{$command} ) ) + { + $DENON_db->{$status}{$command} = $value; + } + elsif ( defined($DENON_db->{$status}{$command}{$info1} ) && $info2 eq "na" && $info3 eq "na" ) { + $DENON_db->{$status}{$command}{$info1} = $value; + } + elsif ( defined($DENON_db->{$status}{$command}{$info1}{$info2}) && $info3 eq "na" ) { + $DENON_db->{$status}{$command}{$info1}{$info2} = $value; + } + elsif ( defined($DENON_db->{$status}{$command}{$info1}{$info2}{$info3}) ) { + $DENON_db->{$status}{$command}{$info1}{$info2}{$info3} = $value; + } + + return undef; +} + +sub +DENON_GetKey($$;$) { + my ( $status, $command, $info) = @_; + + if ( defined($status) && defined($command) && !defined($info)) + { + my @keys = keys %{$DENON_db->{$status}}; + my @values = values %{$DENON_db->{$status}}; + while (@keys) { + my $fhemCommand = pop(@keys); + my $denonCommand = pop(@values); + if ($command eq $denonCommand) + { + return $fhemCommand; + } + } + } + if ( defined($status) && defined($command) && defined($info)) + { + my @keys = keys %{$DENON_db->{$status}{$command}}; + my @values = values %{$DENON_db->{$status}{$command}}; + while (@keys) { + my $fhemCommand = pop(@keys); + my $denonCommand = pop(@values); + if ($info eq $denonCommand) + { + return $fhemCommand; + } + } + } + else { + return undef; + } +} + + +################################### +sub +DENON_AVR_Initialize($) +{ + my ($hash) = @_; + + Log 5, "DENON_AVR_Initialize: Entering"; + + require "$attr{global}{modpath}/FHEM/DevIo.pm"; + +# Provider + $hash->{ReadFn} = "DENON_AVR_Read"; + $hash->{WriteFn} = "DENON_AVR_Write"; + $hash->{ReadyFn} = "DENON_AVR_Ready"; + +# Device + $hash->{DefFn} = "DENON_AVR_Define"; + $hash->{UndefFn} = "DENON_AVR_Undefine"; + $hash->{GetFn} = "DENON_AVR_Get"; + $hash->{SetFn} = "DENON_AVR_Set"; + $hash->{NotifyFn} = "DENON_AVR_Notify"; + $hash->{ShutdownFn} = "DENON_AVR_Shutdown"; + + $hash->{AttrList} = "brand:Denon,Marantz disable:0,1 do_not_notify:1,0 connectionCheck:off,30,45,60,75,90,105,120,240,300 dlnaName favorites maxFavorites maxPreset inputs playTime:off,1,2,3,4,5,10,15,20,30,40,50,60 sleep timeout:1,2,3,4,5 presetMode:numeric,alphanumeric type:AVR,Ceol unit:off,on ".$readingFnAttributes; + + $data{RC_makenotify}{DENON_AVR} = "DENON_AVR_RCmakenotify"; + $data{RC_layout}{DENON_AVR_RC} = "DENON_AVR_RClayout"; +} + +################################### +sub +DENON_AVR_Define($$) +{ + my ($hash, $def) = @_; + + Log 5, "DENON_AVR_Define($def) called."; + + my @a = split("[ \t][ \t]*", $def); + + if (@a != 3) + { + my $msg = "wrong syntax: define DENON_AVR "; + Log 2, $msg; + + return $msg; + } + + RemoveInternalTimer($hash); + DevIo_CloseDev($hash); + + my $name = $a[0]; + + $hash->{Clients} = ":DENON_AVR_ZONE:"; + $hash->{TIMEOUT} = AttrVal( $name, "timeout", "3" ); + $hash->{DeviceName} = $a[2]; + + $hash->{helper}{isPlaying} = 0; + $hash->{helper}{isPause} = 0; + $hash->{helper}{playTimeCheck} = 0; + + $modules{DENON_AVR_ZONE}{defptr}{$name}{1} = $hash; + + InternalTimer(gettimeofday() + 5, "DENON_AVR_UpdateConfig", $hash, 0); + + unless (exists($attr{$name}{webCmd})){ + $attr{$name}{webCmd} = 'volume:muteT:input:surroundMode'; + } + unless ( exists( $attr{$name}{cmdIcon} ) ) { + $attr{$name}{cmdIcon} = 'muteT:rc_MUTE'; + } + unless ( exists( $attr{$name}{devStateIcon} ) ) { + $attr{$name}{devStateIcon} = 'on:rc_GREEN:main_off main_off:rc_YELLOW:main_on off:rc_STOP:main_on absent:rc_RED:main_on muted:rc_MUTE@green:muteT playing:rc_PLAY@green:pause paused:rc_PAUSE@green:play disconnected:rc_RED'; + } + unless (exists($attr{$name}{stateFormat})){ + $attr{$name}{stateFormat} = 'stateAV'; + } + + + # connect using serial connection (old blocking style) + if ($hash->{DeviceName} =~ /^([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}) (.+@.+)/) + { + my $ret = DevIo_OpenDev($hash, 0, "DENON_AVR_DoInit"); + return $ret; + } + # connect using TCP connection (non-blocking style) + else + { + $hash->{DeviceName} = $hash->{DeviceName} . ":23" + if ( $hash->{DeviceName} !~ m/^(.+):([0-9]+)$/ ); + + DevIo_OpenDev( + $hash, 0, + "DENON_AVR_DoInit", + sub() { + my ( $hash, $err ) = @_; + Log3 $name, 4, "DENON_AVR $name: $err." if ($err); + } + ); + } + +} + +##################################### +sub +DENON_AVR_DoInit($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + if ( lc( ReadingsVal( $name, "state", "?" ) ) eq "opened" ) { + DoTrigger( $name, "CONNECTED" ); + Log3 $name, 5, "DENON_AVR_DoInit $name: CONNECTED"; + } + else { + DoTrigger( $name, "DISCONNECTED" ); + Log3 $name, 5, "DENON_AVR_DoInit $name: DISCONNECTED"; + } +} + +sub +DENON_AVR_Notify($$) { + my ( $hash, $dev ) = @_; + my $name = $hash->{NAME}; + my $devName = $dev->{NAME}; + + if ( $devName eq "global" ) { + foreach my $change ( @{ $dev->{CHANGED} } ) { + if ($change =~ /^(.+) (.+) (.+) (.+)/) { + if ($1 eq "ATTR") + { + if ($2 eq $name) + { + if ($3 eq "connectionCheck") + { + if ($4 ne "off") + { + RemoveInternalTimer($hash, "DENON_AVR_ConnectionCheck"); + InternalTimer(gettimeofday() + $4, "DENON_AVR_ConnectionCheck", $hash, 0); + Log3 $name, 5, "DENON_AVR $name: changing attribut connectionCheck to <$4> seconds."; + } + else + { + RemoveInternalTimer($hash, "DENON_AVR_ConnectionCheck"); + Log3 $name, 5, "DENON_AVR $name: changing attribut connectionCheck to off."; + } + } + elsif ($3 eq "brand") + { + Log3 $name, 5, "DENON_AVR $name: changing attribut brand to <$4>."; + } + elsif ($3 eq "disable") + { + if ($4 eq "1") + { + RemoveInternalTimer($hash); + DevIo_CloseDev($hash); + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "power", "off"); + readingsBulkUpdate($hash, "presence", "absent"); + readingsBulkUpdate($hash, "state", "disconnected"); + readingsBulkUpdate($hash, "stateAV", DENON_AVR_GetStateAV($hash)); + readingsEndUpdate($hash, 1); + + if ( defined($modules{DENON_AVR_ZONE}{defptr}{$name}{2}) || defined($modules{DENON_AVR_ZONE}{defptr}{$name}{3})) + { + Log3 $name, 5, "DENON_AVR $name: Dispatching state change to slaves"; + Dispatch( $hash, "presence absent", undef); + Dispatch( $hash, "power off", undef); + Dispatch( $hash, "state disconnected", undef); + Dispatch( $hash, "stateAV ".DENON_AVR_GetStateAV($hash), undef); + } + } + else + { + DevIo_OpenDev($hash, 0, "DENON_AVR_DoInit"); + } + Log3 $name, 5, "DENON_AVR $name: changing attribut disable to <$4>."; + } + elsif ($3 eq "playTime") + { + RemoveInternalTimer($hash, "DENON_AVR_PlaytimeCheck"); + if ($4 ne "off") + { + RemoveInternalTimer($hash, "DENON_AVR_PlaytimeCheck"); + InternalTimer(gettimeofday() + $4, "DENON_AVR_PlaytimeCheck", $hash, 0); + } + Log3 $name, 5, "DENON_AVR $name: changing attribut playTime to <$4>."; + } + elsif ($3 eq "maxFavorites") + { + Log3 $name, 5, "DENON_AVR $name: changing attribut maxFavorites to <$4>."; + } + elsif ($3 eq "maxPreset") + { + Log3 $name, 5, "DENON_AVR $name: changing attribut maxPreset to <$4>."; + } + elsif ($3 eq "presetMode") + { + Log3 $name, 5, "DENON_AVR $name: changing attribut presetMode to <$4>."; + } + elsif ($3 eq "timeout") + { + $hash->{TIMEOUT} = $4; + Log3 $name, 5, "DENON_AVR $name: changing attribut timeout to <$4>."; + } + elsif ($3 eq "type") + { + if ($4 eq "AVR") + { + $attr{$name}{devStateIcon} = 'on:rc_GREEN:main_off main_off:rc_YELLOW:main_on off:rc_STOP:main_on absent:rc_RED:main_on muted:rc_MUTE@green:muteT playing:rc_PLAY@green:pause paused:rc_PAUSE@green:play disconnected:rc_RED'; + } + elsif ($4 eq "Ceol") + { + $attr{$name}{devStateIcon} = 'on:rc_GREEN:off off:rc_YELLOW:on off:rc_STOP:on absent:rc_RED:on muted:rc_MUTE@green:muteT playing:rc_PLAY@green:pause paused:rc_PAUSE@green:play disconnected:rc_RED'; + } + + Log3 $name, 5, "DENON_AVR $name: changing attribut type to <$4>."; + } + } + } + } + elsif ($change =~ /^(.+) (.+) (.+)/) + { + if ($1 eq "DELETEATTR") + { + if ($2 eq $name) + { + if ($3 eq "connectionCheck") + { + RemoveInternalTimer($hash, "DENON_AVR_ConnectionCheck"); + InternalTimer(gettimeofday() + 60, "DENON_AVR_ConnectionCheck", $hash, 0); + Log3 $name, 5, "DENON_AVR $name: changing attribut connectionCheck to <60> seconds."; + } + elsif ($3 eq "brand") + { + Log3 $name, 5, "DENON_AVR $name: changing attribut brand to ."; + } + elsif ($3 eq "disable") + { + InternalTimer(gettimeofday() + 5, "DENON_AVR_UpdateConfig", $hash, 0); + } + elsif ($3 eq "timeout") + { + $hash->{TIMEOUT} = 3; + Log3 $name, 5, "DENON_AVR $name: deleting attribut timeout."; + } + elsif ($3 eq "dlnaName") + { + Log3 $name, 5, "DENON_AVR $name: deleting attribut dlnaName."; + } + elsif ($3 eq "type") + { + $attr{$name}{devStateIcon} = 'on:rc_GREEN:main_off main_off:rc_YELLOW:main_on off:rc_STOP:main_on absent:rc_RED:main_on muted:rc_MUTE@green:muteT playing:rc_PLAY@green:pause paused:rc_PAUSE@green:play disconnected:rc_RED'; + Log3 $name, 5, "DENON_AVR $name: deleting attribut type."; + } + } + } + } + } + } + elsif ( $devName ne $name ) { + return; + } + + foreach my $change ( @{ $dev->{CHANGED} } ) { + + readingsBeginUpdate($hash); + + if ($change eq "CONNECTED") + { + Log3 $hash, 5, "DENON_AVR " . $name . ": processing change $change"; + InternalTimer(gettimeofday() + 5, "DENON_AVR_UpdateConfig", $hash, 0); + } + elsif ($change eq "DISCONNECTED") + { + RemoveInternalTimer($hash); + + readingsBulkUpdate($hash, "power", "off"); + readingsBulkUpdate($hash, "presence", "absent"); + readingsBulkUpdate($hash, "state", "disconnected"); + readingsBulkUpdate($hash, "stateAV", DENON_AVR_GetStateAV($hash)); + + if ( defined($modules{DENON_AVR_ZONE}{defptr}{$name}{2}) || defined($modules{DENON_AVR_ZONE}{defptr}{$name}{3})) + { + Log3 $name, 5, "DENON_AVR $name: Dispatching state change to slaves"; + Dispatch( $hash, "presence absent", undef); + Dispatch( $hash, "power off", undef); + Dispatch( $hash, "state disconnected", undef); + Dispatch( $hash, "stateAV ".DENON_AVR_GetStateAV($hash), undef); + } + } + elsif ($change =~ /^(.+): (.+)/) { + + if ($1 eq "currentMedia") + { + my $status = defined($hash->{helper}{playTimeCheck}) ? $hash->{helper}{playTimeCheck} : 0; + if($status == 0) + { + readingsBulkUpdate($hash, "playStatus", "stopped"); + } + } + if ($1 eq "currentPlaytime") + { + my $status = ReadingsVal($name, "playStatus", "stopped"); + if ($2 eq "-") + { + readingsBulkUpdate($hash, "playStatus", "stopped") if($status ne "stopped"); + my $cover = "http://" . $hash->{helper}{deviceIP} . "/img/album%20art_S.png" . "?" . DENON_AVR_GetTimeStamp(gettimeofday()); + readingsBulkUpdate($hash, "currentCover", $cover); + } + elsif ($hash->{helper}{isPause} == 1) + { + readingsBulkUpdate($hash, "playStatus", "paused") if($status ne "paused"); + } + else + { + readingsBulkUpdate($hash, "playStatus", "playing") if($status ne "playing"); + } + } + elsif ($1 eq "mute") + { + readingsBulkUpdate($hash, "stateAV", DENON_AVR_GetStateAV($hash)); + } + elsif ($1 eq "playStatus") + { + if ($2 eq "playing") + { + $hash->{helper}{isPlaying} = 1; + $hash->{helper}{isPause} = 0; + $hash->{helper}{playTimeCheck} = 1; + } + elsif ($1 eq "paused") + { + $hash->{helper}{isPlaying} = 1; + $hash->{helper}{isPause} = 1; + $hash->{helper}{playTimeCheck} = 1; + } + elsif ($1 eq "stopped") + { + $hash->{helper}{isPlaying} = 0; + $hash->{helper}{isPause} = 0; + $hash->{helper}{playTimeCheck} = 0; + } + + readingsBulkUpdate($hash, "stateAV", DENON_AVR_GetStateAV($hash)); + } + elsif ($1 eq "power") + { + readingsBulkUpdate($hash, "stateAV", DENON_AVR_GetStateAV($hash)); + } + elsif ($1 eq "state") + { + readingsBulkUpdate($hash, "stateAV", DENON_AVR_GetStateAV($hash)); + } + } + } + readingsEndUpdate($hash, 1); + + return; +} + +##################################### +sub +DENON_AVR_Ready($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + if ( lc(ReadingsVal( $name, "state", "disconnected" )) eq "disconnected" ) { + + DevIo_OpenDev( + $hash, 1, undef, + sub() { + my ( $hash, $err ) = @_; + Log3 $name, 4, "DENON_AVR $name: $err." if ($err); + } + ); + return; + } + + # This is relevant for windows/USB only + my $po = $hash->{USBDev}; + my ( $BlockingFlags, $InBytes, $OutBytes, $ErrorFlags ); + if ($po) { + ( $BlockingFlags, $InBytes, $OutBytes, $ErrorFlags ) = $po->status; + } + return ( $InBytes && $InBytes > 0 ); +} + +################################### +sub +DENON_AVR_Read($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + my $state = ReadingsVal( $name, "power", "off" ); + my $buf = ''; + my $zone = 0; + my $return; + + if(defined($hash->{helper}{PARTIAL}) && $hash->{helper}{PARTIAL}) { + $buf = $hash->{helper}{PARTIAL} . DevIo_SimpleRead($hash); + } else { + $buf = DevIo_SimpleRead($hash); + } + return if(!defined($buf)); + + my $checkInterval = AttrVal( $name, "connectionCheck", "60" ); + RemoveInternalTimer($hash, "DENON_AVR_ConnectionCheck"); + if ($checkInterval ne "off" ) { + my $next = gettimeofday() + $checkInterval; + $hash->{helper}{nextConnectionCheck} = $next; + InternalTimer($next, "DENON_AVR_ConnectionCheck", $hash, 0); + } + + Log3 $name, 5, "DENON_AVR $name: read."; + + readingsBeginUpdate($hash); + while ($buf =~ m/\r/) + { + my $rmsg; + ($rmsg, $buf) = split("\r", $buf, 2); + $rmsg =~ s/^\s+|\s+$//g; + $rmsg =~ s/\s+/ /g; + + if ($rmsg =~ /^Z2|Z3|Z4/) { + if ( defined($modules{DENON_AVR_ZONE}{defptr}{$name}{2}) && $rmsg =~ /^Z2/ ) + { + Log3 $hash, 4, "DENON_AVR $name dispatch: this is for zone 2 <$rmsg>"; + Dispatch( $hash, $rmsg, undef ); + } + elsif ( defined($modules{DENON_AVR_ZONE}{defptr}{$name}{3}) && $rmsg =~ /^Z3/ ) + { + Log3 $hash, 4, "DENON_AVR $name dispatch: this is for zone 3 <$rmsg>"; + Dispatch( $hash, $rmsg, undef ); + } + elsif ( defined($modules{DENON_AVR_ZONE}{defptr}{$name}{4}) && $rmsg =~ /^Z4/ ) + { + Log3 $hash, 4, "DENON_AVR $name dispatch: this is for zone 4 <$rmsg>"; + Dispatch( $hash, $rmsg, undef ); + } + + if ($rmsg =~ /(Z[2-4])(ON$|OFF$)/) { + + $return = DENON_AVR_Parse($hash, $rmsg) if($rmsg); + + Log3 $hash, 4, "DENON_AVR zone $1: parsing <$rmsg> to <$return>."; + } + readingsEndUpdate($hash, 1); + return; + } + + $return = DENON_AVR_Parse($hash, $rmsg) if($rmsg); + Log3 $name, 4, "DENON_AVR $name: parsing <$rmsg> to <$return>." if($rmsg); + } + + readingsEndUpdate($hash, 1); + $hash->{helper}{PARTIAL} = $buf; +} + +#################################### +sub +DENON_AVR_Write($$;$) +{ + my ($hash, $msg, $event) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 4, "DENON_AVR $name: SimpleWrite $msg <$event>."; + + $msg = $msg."\r"; + + DevIo_SimpleWrite($hash, $msg, 0); + + # do connection check latest after TIMEOUT + my $next = gettimeofday() + $hash->{TIMEOUT}; + if ( !defined( $hash->{helper}{nextConnectionCheck} ) + || $hash->{helper}{nextConnectionCheck} > $next ) + { + $hash->{helper}{nextConnectionCheck} = $next; + RemoveInternalTimer($hash, "DENON_AVR_ConnectionCheck"); + InternalTimer( $next, "DENON_AVR_ConnectionCheck", $hash, 0 ); + } +} + +################################### +sub +DENON_AVR_Parse(@) +{ + my ($hash, $msg) = @_; + my $name = $hash->{NAME}; + my $deviceIP = $hash->{DeviceName}; + $deviceIP =~ /^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|[a-zA-Z0-9_-]+\.local):\d+$/; + $hash->{helper}{deviceIP} = $1; + my $percent = AttrVal($name, "unit", "off") eq "on" ? " %" : ""; + my $dezibel = AttrVal($name, "unit", "off") eq "on" ? " dB" : ""; + my $return = "unknown"; + + #Power + if ($msg =~ /^PW(.+)/) + { + my $power = lc($1); + if ($power eq "standby") + { + $power = "off"; + } + readingsBulkUpdate($hash, "power", $power); + readingsBulkUpdate($hash, "state", $power); + DENON_AVR_GetStateAV($hash); + + $return = $power; + } + #Channel-Level (older devices) + elsif ($msg =~ /^CV([A-Z2]+) (.+)/){ + my $channel = DENON_GetValue('CV', $1); + my $volume = $2; + if (length($volume) == 2) + { + $volume = $volume."0"; + } + readingsBulkUpdate($hash, "level".$channel, ($volume / 10 - 50).$dezibel) if($channel ne "unknown"); + $return = "level".$channel." ".($volume / 10 - 50); + } + #Channel-Level (newer devices) + elsif ($msg =~ /^SSLEV([A-Z2]+) (.+)/){ + my $channel = DENON_GetValue('SSLEV', $1); + my $volume = $2; + if (length($volume) == 2) + { + $volume = $volume."0"; + } + readingsBulkUpdate($hash, "Level-".$channel, ($volume / 10 - 50).$dezibel) if($channel ne "unknown"); + $return = "level".$channel." ".($volume / 10 - 50); + } + #digitalInput + elsif ($msg =~ /^DC(.+)/) + { + my $digitalInput = DENON_GetValue('DC', $1); + readingsBulkUpdate($hash, "digitalInput", $digitalInput) if($digitalInput ne "unknown"); + $return = "digitalInput ".$digitalInput; + } + #favorite (only older models) + elsif ($msg =~ /^ZMFAVORITE(.+)/) + { + readingsBulkUpdate($hash, "favorite", $1); + $return = "favorite ".$1; + } + #Mute + elsif ($msg =~ /^MU(.+)/) + { + readingsBulkUpdate($hash, "mute", lc($1)); + $return = lc($1); + } + #Maximal Volume + elsif ($msg =~ /^MVMAX (.+)/) + { + readingsBulkUpdate($hash, "volumeMax", $1.$percent); + $return = "volumeMax ".$1; + } + #Volume + elsif ($msg =~ /^MV(.+)/) + { + my $volume = $1; + if (length($volume) == 2) + { + $volume = $volume."0"; + } + readingsBulkUpdate($hash, "volumeStraight", ($volume / 10 - 80).$percent); + readingsBulkUpdate($hash, "volume", ($volume / 10).$dezibel); + $return = "volume/volumeStraight ".($volume / 10)."/".($volume / 10 - 80); + $hash->{helper}{volume} = $volume / 10; + } + #Sound Parameter + elsif ($msg =~ /^PS(.+)/) + { + my $parameter = $1; + if($parameter =~ /^(TONE CTRL) (.+)/) + { + my $status = DENON_GetValue('PS', $1); + readingsBulkUpdate($hash, $status, lc($2)) if($status ne "unknown"); + $return = $status." ".lc($2); + } + elsif($parameter =~ /^([A-Z]{3}) (.+)/) + { + if($2 eq 'ON' || $2 eq 'OFF') + { + my $status = DENON_GetValue('PS', $1); + readingsBulkUpdate($hash, $status, lc($2)) if($status ne "unknown"); + $return = $status." ".lc($2); + } + elsif($1 eq "PHG") + { + my $status = DENON_GetValue('PS', $1, $1); + my $value = DENON_GetValue('PS', $1, $2); + readingsBulkUpdate($hash, $status, $value) if($status ne "unknown" || $value ne "unknown"); + $return = $status." ".$value; + } + elsif($1 eq "BAL") + { + my $status = DENON_GetValue('PS', $1); + my $volume = $2 - 50; + readingsBulkUpdate($hash, $status, $volume) if($status ne "unknown"); + $return = $status." ".$volume; + } + else + { + my $status = DENON_GetValue('PS', $1); + my $volume = $2; + + if($1 eq 'LFE') + { + $volume = ($volume * -1).$dezibel; + } + elsif($1 eq 'EFF') + { + $volume = $volume.$dezibel; + } + elsif($1 eq 'DEL') + { + $volume = $volume." ms"; + } + elsif($1 eq 'CLV') + { + $volume = ($volume -50).$dezibel; + } + else + { + if (length($volume) == 2) + { + $volume = $volume."0"; + } + $volume = ($volume / 10 - 50).$dezibel; + } + readingsBulkUpdate($hash, $status, $volume) if($status ne "unknown"); + $return = $status." ".$volume; + } + } + elsif($parameter =~ /^(CINEMA EQ).(.+)/) + { + my $name = DENON_GetValue('PS', $1); + readingsBulkUpdate($hash, $name, lc($2)) if($name ne "unknown"); + $return = $name." ".lc($2); + } + elsif($parameter =~ /^(MULTEQ):(.+)/) + { + my $name = DENON_GetValue('PS', $1, $1); + my $status = DENON_GetValue('PS', $1, $2); + readingsBulkUpdate($hash, $name, $status) if($name ne "unknown" || $status ne "unknown"); + $return = $name." ".$status; + } + elsif($parameter =~ /^(DYNEQ) (.+)/) + { + my $name = DENON_GetValue('PS', $1); + readingsBulkUpdate($hash, $name, lc($2)) if($name ne "unknown"); + $return = $name." ".lc($2); + } + elsif($parameter =~ /^(DYNVOL) (.+)/) + { + my $name = DENON_GetValue('PS', $1, $1); + my $status = DENON_GetValue('PS', $1, $2); + readingsBulkUpdate($hash, $name, $status) if($name ne "unknown" || $status ne "unknown"); + $return = $name." ".$status; + } + } + #Input select + elsif ($msg =~ /^SI(.+)/) + { + my $status = DENON_GetKey('SI', $1); + readingsBulkUpdate($hash, "input", $status) if($status ne "unknown"); + readingsBulkUpdate($hash, "currentStream", "-") if($status ne "Server"); + $hash->{helper}{INPUT} = $1; + $return = $status; + + if ($1 =~ /^(TUNER|DVD|BD|TV|SAT\/CBL|GAME|SAT|AUX1|AUX2|AUX3|AUX4|AUX5|AUX6|AUX7|FLICKR)$/) + { + for(my $i = 0; $i < 9; $i++) { + my $cur = ""; + my $status = 'ignore'; + if ($i == 2) + { + $cur = "s"; + $status = DENON_GetValue('NSE', $i.$cur); + if($status ne 'ignore'){ + readingsBulkUpdate($hash, $status, '-'); + } + $cur = "a"; + } + $status = DENON_GetValue('NSE', $i.$cur); + if($status ne 'ignore'){ + readingsBulkUpdate($hash, $status, '-'); + } + } + } + } + #Video-Select + elsif ($msg =~ /^SV(.+)/) + { + my $status = DENON_GetValue('SV', $1); + readingsBulkUpdate($hash, "videoSelect", $status) if($status ne "unknown"); + $return = "videoSelect ".$status; + } + #Setup-Menu + elsif ($msg =~ /^MNMEN ([A-Z]+)/) + { + readingsBulkUpdate($hash, "setup", lc($1)); + $return = "setup ".lc($1); + } + elsif ($msg =~ /^MNZST ([A-Z]+)/) + { + readingsBulkUpdate($hash, "allZoneStereo", lc($1)); + $return = "setup ".lc($1); + } + #quickselect + elsif ($msg =~ /^MSQUICK(.+)/) + { + my $quick = DENON_GetValue("MS", "QUICK".$1); + if ($1 =~ /^(1|2|3|4)/) { + readingsBulkUpdate($hash, "quickselect", $quick) if($quick ne "unknown"); + $return = "quickselect ".$quick; + } + } + #smartselect (Marantz) + elsif ($msg =~ /^MSSMART(.+)/) + { + my $quick = DENON_GetValue("MS", "SMART".$1); + if ($1 =~ /^(1|2|3|4)/) { + readingsBulkUpdate($hash, "smartselect", $quick) if($quick ne "unknown"); + $return = "smartselect ".$quick; + } + } + #Sound + elsif ($msg =~ /^MS(.+)/) + { + my $sound = DENON_GetValue('SOUND', $1); + # get Surround-Mode from MS + if($sound eq "unknown") + { + $sound = DENON_GetKey('MS', $1); + } + if ($sound ne "unknown") + { + readingsBulkUpdate($hash, "sound", $sound); + $return = "sound ".$sound; + } + } + #tuner band + elsif ($msg =~ /^TMAN(.+)/) + { + my $tuner = DENON_GetKey('TM', 'AN', $1); + if($tuner =~ /^(AM|FM)$/) + { + readingsBulkUpdate($hash, "tunerBand", $tuner) if($tuner ne "unknown"); + $return = "tunerBand ".$tuner; + } + else + { + readingsBulkUpdate($hash, "tunerMode", $tuner) if($tuner ne "unknown"); + $return = "tunerMode ".$tuner; + } + } + #tuner preset + elsif ($msg =~ /^TPAN([0-9]+)/) + { + readingsBulkUpdate($hash, "tunerPreset", ($1 / 1)); + $return = "tunerPreset ".$1; + + } + elsif ($msg =~ /^TPAN(OFF|ON)/) + { + readingsBulkUpdate($hash, "tunerTrafficProgramme", lc($1)); + $return = "tunerTrafficProgramme ".lc($1); + + } + #tuner preset memory + elsif ($msg =~ /^TPANMEM([A-Z0-9]+)/) + { + readingsBulkUpdate($hash, "tunerPresetMemory", $1); + $return = "tunerPresetMemory ".$1; + + } + #tuner frequency + elsif ($msg =~ /^TFAN([0-9]{6})/) + { + my $frq = $1 / 100; + if ($frq > 500) + { + readingsBulkUpdate($hash, "tunerFrequency", $frq." kHz"); + $return = "tunerFrequency ".$frq." kHz"; + } + else + { + readingsBulkUpdate($hash, "tunerFrequency", $frq." MHz"); + $return = "tunerFrequency ".$frq." MHz"; + } + + } + elsif ($msg =~ /^TFANNAME(.+)/) #TFANNAMEBayern 2 + { + readingsBulkUpdate($hash, "tunerStationName", $1); + $return = "tunerStationName ".$1; + } + #ECO-Mode + elsif ($msg =~ /^ECO([A-Z]+)/) + { + readingsBulkUpdate($hash, "eco", lc($1)); + $return = "eco ".lc($1); + } + #Dim-Mode Display + elsif ($msg =~ /^DIM.([A-Z]+)/) + { + my $status = DENON_GetKey('DIM', $1); + readingsBulkUpdate($hash, "display", $status) if($status ne "unknown"); + $return = "display ".$status; + } + #autoStandby + elsif ($msg =~ /^STBY(.+)/) + { + my $status = DENON_GetKey('STBY', $1); + readingsBulkUpdate($hash, "autoStandby", $status) if($status ne "unknown"); + $return = "autoStandby ".$status; + } + #sleep + elsif ($msg =~ /^SLP(.+)/) + { + my $status = lc($1); + if ($status ne "off") + { + $status = ($1 / 1)."min"; + } + readingsBulkUpdate($hash, "sleep", $status) if($status ne "unknown"); + $return = "sleep ".$status; + } + #trigger on/off + elsif ($msg =~ /^TR([0-9]) (.+)/) + { + readingsBulkUpdate($hash, "trigger".$1, lc($2)); + $return = "trigger".$1." ".lc($2); + } + #mainzone on/off + elsif ($msg =~ /^ZM(.+)/) + { + readingsBulkUpdate($hash, "zoneMain", lc($1)) if(lc($1) ne ReadingsVal( $name, "zoneMain", "off")); + readingsBulkUpdate($hash, "stateAV", DENON_AVR_GetStateAV($hash)); + DENON_AVR_GetStateAV($hash); + $return = "zoneMain ". lc($1); + } + # other zones + elsif ($msg =~ /^Z([2-4])(.+)/) + { + if($2 eq "ON"|| $2 eq "OFF") + { + Log3 $hash, 5, "DENON_AVR $name zone$1: $msg"; + readingsBulkUpdate($hash, "zone".$1, lc($2)); + $return = "zone".$1 . " " . lc($2); + } + } + #current Media + elsif ($msg =~ /^NSE0/ && length($msg) > 5){ + my $text = substr($msg,4); + if ($text =~ /^Now Playing/) { + if(ReadingsVal( $name, "input", "" ) =~ /^(iRadio|Mediaplayer|HDRadio|OnlineMusic|Spotify|LastFM|Server|Favorites|SiriusXM|Bluetooth|Usb\/iPod|Usb_play|iPod_play|iRadio_play|Favorites_play|Pandora)$/) + { + $text =~ /Now Playing (.+)/; + my $status = DENON_GetValue('NSE', '0'); + readingsBulkUpdate($hash, $status, $1) if ($1 ne ReadingsVal( $name, "currentMedia", "-")); + if(ReadingsVal( $name, "currentPlaytime", "-") eq "-") + { + readingsBulkUpdate($hash, "playStatus", "playing"); + readingsBulkUpdate($hash, "currentPlaytime", "0:00"); + DENON_AVR_PlaytimeCheck ($hash); + $hash->{helper}{playTimeCheck} = 1; + $hash->{helper}{isPlaying} = 1; + } + $return = $status." ".$1; + } + else + { + my $cover = "http://" . $hash->{helper}{deviceIP} . "/img/album%20art_S.png" . "?" . DENON_AVR_GetTimeStamp(gettimeofday()); + readingsBulkUpdate($hash, "currentCover", $cover); + + $return = "reading ignored"; + } + } + else + { + foreach my $key (sort(keys %{$DENON_db->{'NSE'}})) { + my $status = DENON_GetValue('NSE', $key); + if ($status ne "ignore" || $status ne "unknown") { + readingsBulkUpdate($hash, $status, "-"); + } + } + + $return = "set readings to '-'"; + } + } + #Media Informations + elsif ($msg =~ /^NSE/ && length($msg) > 5){ + my $flag = ord(substr($msg,4,1)); + + if(substr($msg,3,1) eq "4" && $flag == 0 && ord(substr($msg,5,1)) > 31) + { + #return "reading ignored"; + } + elsif(substr($msg,3,1) eq "1" && $flag == 1 && ord(substr($msg,5,1)) > 31) + { + #return "reading ignored"; + } + elsif(substr($msg,3,1) ne "5" && (ord(substr($msg,5,1)) == 32 || $flag == 0)) + { + return "reading ignored: flag: " . $flag; + } + + my $text = $flag < 32 ? substr($msg,5) : substr($msg,4); + my $cur = ""; + my $status = ""; + + if(ReadingsVal( $name, "input", "" ) =~ /^(iRadio|Mediaplayer|HDRadio|OnlineMusic|Spotify|LastFM|Server|Favorites|SiriusXM|Bluetooth|Usb\/iPod|Usb_play|iPod_play|iRadio_play|Favorites_play|Pandora)$/) + { + if (substr($msg,3,1) eq '2') + { + if(ReadingsVal( $name, "currentMedia", "" ) eq "iRadio" || ReadingsVal( $name, "input", "" ) eq "iRadio") + { + $cur = "s"; + $status = DENON_GetValue('NSE', substr($msg,3,1)."a"); + readingsBulkUpdate($hash, $status, "-") if ("-" ne ReadingsVal( $name, "currentArtist", "-")); + $status = DENON_GetValue('NSE', "4"); + readingsBulkUpdate($hash, $status, "-") if ("-" ne ReadingsVal( $name, "currentAlbum", "-")); + } + else + { + $cur = "a"; + $status = DENON_GetValue('NSE', substr($msg,3,1)."s"); + readingsBulkUpdate($hash, $status, "-") if ("-" ne ReadingsVal( $name, "currentStation", "-")); + } + } + + $status = DENON_GetValue('NSE', substr($msg,3,1).$cur); + + if (substr($msg,3,1) ne '0') + { + if ((substr($msg,3,1) eq '5' && $text =~ /([0-9]{1,2}:[0-9]{2}:[0-9]{2}) .+/) || (substr($msg,3,1) eq '5' && $text =~ /([0-9]{1,2}:[0-9]{2}) .+/)) #Playtime + { + if ($flag eq "0") # 0 = NUL (time) or 2 = STX (text) + { + if ($hash->{helper}{playTimeCheck} == 1) + { + DENON_AVR_PlaystatusCheck($hash); + readingsBulkUpdate($hash, $status, $1) if ($1 ne ReadingsVal( $name, "currentPlaytime", "-")); + $return = $status." ".$1." flag: ".$flag; + } + else + { + readingsBulkUpdate($hash, $status, $1) if ($1 ne ReadingsVal( $name, "currentPlaytime", "-")); + $return = $status." ".$1." flag: ".$flag; + } + } + } + elsif($status ne 'ignore'){ + my $cover = "http://" . $hash->{helper}{deviceIP} . "/NetAudio/art.asp-jpg" . "?" . DENON_AVR_GetTimeStamp(gettimeofday()); + if (substr($msg,3,1) eq '1' && $flag eq "1" && $hash->{helper}{isPlaying} == 1) #Title + { + if ($text ne ReadingsVal( $name, "currentTitle", "-")) { + readingsBulkUpdate($hash, $status, $text); + readingsBulkUpdate($hash, "currentCover", $cover); + } + } + if (substr($msg,3,1) eq '2' && $flag eq "1" && $hash->{helper}{isPlaying} == 1) #Artist or station + { + if($cur eq "a" && $text ne ReadingsVal( $name, "currentArtist", "-")) + { + readingsBulkUpdate($hash, $status, $text); + readingsBulkUpdate($hash, "currentCover", $cover); + } + if($cur eq "s" && $text ne ReadingsVal( $name, "currentStation", "-")) + { + readingsBulkUpdate($hash, $status, $text); + readingsBulkUpdate($hash, "currentCover", $cover); + } + } + if (substr($msg,3,1) eq '3' && $flag eq "1" && $text =~ /kHz/ && $hash->{helper}{isPlaying} == 1) #Bitrate + { + readingsBulkUpdate($hash, $status, $text) if ($text ne ReadingsVal( $name, "currentBitrate", "-")); + } + if (substr($msg,3,1) eq '4' && $flag eq "0" && $hash->{helper}{isPlaying} == 1) # Album + { + readingsBulkUpdate($hash, $status, $text) if ($text ne ReadingsVal( $name, "currentAlbum", "-")); + } + $return = $status." ".$text." flag: ".$flag; + } + else + { + $return = "reading ignored: flag: " . $flag; + } + } + } + } + #system information + elsif ($msg =~ /^SS(.+)/){ + my $parameter = $1; + if($parameter =~ /^([A-Z]{3}) (.+)/) + { + if($1 eq 'LOC') #SSLOC OFF + { + my $status = DENON_GetValue('SS', $1); + readingsBulkUpdate($hash, $status, lc($2)) if($status ne "unknown"); + $return = $status." ".lc($2); + } + #Surround Mode + elsif($1 eq 'SMG') #SSSMG MOV + { + my $status = DENON_GetValue('SS', $1, $2); + readingsBulkUpdate($hash, "surroundMode", $status) if($status ne "unknown"); + $return = "surroundMode ".$status; + } + } + elsif($parameter =~ /^([A-Z]{3})(.+) (.+)/) + { + if ($1 eq 'FUN') # SSFUNCD CD , SSFUNMPLAY Media Player + { + my $function = $3; + $function =~ s/ //g; + #DENON_SetValue($function, 'SS', $1, $2); + $return = "name ".$2." changed to ".$function; + } + elsif ($1 eq 'SOD') # SSSODTUNER USE + { + #DENON_SetValue($3, 'SS', $1, $2); + $return = lc($3)." ".$2; + } + elsif ($1 eq 'PAA') # SSPAA + { + my $status = DENON_GetValue('SS', $1, $2, $3); + readingsBulkUpdate($hash, "ampAssign", $status) if($status ne "unknown"); + $return = "ampAssign ".$status; + } + elsif ($1 eq 'INF') # SSINFFRM 0000-0000-0000-00 + { + #Firmware + if ($2 eq "FRM") { # SSINFFRM 0000-0000-0000-00 + my $status = DENON_GetValue('SS', $1, $2); + readingsBulkUpdate($hash, $status, $3) if($status ne "unknown"); + $return = $status." ".$3; + } + else # SSINFAISSIG 02 + { + my $cmd1 = $1; #INF + my $cmd2 = $2; + my $value = $3; # $1 $2 + $cmd2 =~ /^([A-Z]{3})([A-Z]{3})/; # AIS SIG + + if ($1 eq 'AIS') + { + #input signal + if ($2 eq 'SIG') + { + my $signal = DENON_GetValue('SS', $cmd1, $1, $2, $value); + readingsBulkUpdate($hash, "signal", $signal) if($signal ne "unknown"); + $return = "signal ".$signal; + if($signal =~ /^na (.+)/) + { + my $sound = ReadingsVal( $name, "sound", "?" ); + Log3 $name, 2, "DENON_AVR $name: unknown input signal <$1>, sound <$sound>."; + } + } + # samplingRate, audioFormat + elsif ($2 eq 'FSV' || $2 eq 'FOR') + { + my $status = DENON_GetValue('SS', $cmd1, $1, $2); + $value = "-" if($value eq "NON"); + if($status eq "samplingRate" && $value ne "-") + { + if ($value =~ /^([0-9]{3})/) + { + $value = ($1 / 10)." khz"; + } + elsif ($value =~ /^([0-9]{2})/) + { + $value = $1." khz"; + } + } + readingsBulkUpdate($hash, $status, $value) if($status ne "unknown"); + $return = $status." ".$value; + } + } + } + } + } + } + #Model + elsif ($msg =~ /^NS([A-Z]{3}) .+ (.+)/){ + my $status = DENON_GetValue('NS', $1); + readingsBulkUpdate($hash, $status, $2) if($status ne "unknown"); + $return = $status." ".$2; + } + elsif ($msg =~ /^VS(.+)/){ + my $cmd = $1; + if($cmd =~ /^(ASP)(.+)/) + { + my $status = DENON_GetValue('VS', $1, $1); + my $value = DENON_GetValue('VS', $1, $2); + readingsBulkUpdate($hash, $status, $value) if($status ne "unknown" || $value ne "unknown"); + $return = $status." ".$value; + } + elsif($cmd =~ /^(MONI)(.+)/) + { + my $status = DENON_GetValue('VS', $1, $1); + my $value = DENON_GetValue('VS', $1, $2); + readingsBulkUpdate($hash, $status, $value) if($status ne "unknown" || $value ne "unknown"); + $return = $status." ".$value; + } + elsif($cmd =~ /^(SCH)(.+)/) + { + my $status = DENON_GetValue('VS', $1, $1); + my $value = DENON_GetValue('VS', $1, $2); + readingsBulkUpdate($hash, $status, $value) if($status ne "unknown" || $value ne "unknown"); + $return = $status." ".$value; + } + elsif($cmd =~ /^(SC)(.+)/) + { + my $status = DENON_GetValue('VS', $1, $1); + my $value = DENON_GetValue('VS', $1, $2); + readingsBulkUpdate($hash, $status, $value) if($status ne "unknown" || $value ne "unknown"); + $return = $status." ".$value; + } + elsif($cmd =~ /^(AUDIO)(.+)/) + { + my $status = DENON_GetValue('VS', $1, $1); + my $value = DENON_GetValue('VS', $1, $2); + readingsBulkUpdate($hash, $status, $value) if($status ne "unknown" || $value ne "unknown"); + $return = $status." ".$value; + } + elsif($cmd =~ /^(VPM)(.+)/) + { + my $status = DENON_GetValue('VS', $1, $1); + my $value = DENON_GetValue('VS', $1, $2); + readingsBulkUpdate($hash, $status, $value) if($status ne "unknown" || $value ne "unknown"); + $return = $status." ".$value; + } + elsif($cmd =~ /^(VST)(.+)/) + { + my $status = DENON_GetValue('VS', $1); + readingsBulkUpdate($hash, $status, lc($2)) if($status ne "unknown"); + $return = $status." ".lc($2); + } + } + #DigitalSound-Select input + elsif ($msg =~ /^SD(.+)/){ + my $status = DENON_GetValue('SD', $1); + readingsBulkUpdate($hash, "inputSound" ,$status) if($status ne "unknown"); + $return = "inputSound ".$status; + } + #Favorite list - only ceol + elsif ($msg =~ /^FV(.+)/){ + $return = $1; + } + else + { + if($msg eq "CVEND") + { + $return = "ignored"; + } + else + { + $return = "unknown message - $msg"; + } + } + return $return; +} + +############################# +sub +DENON_AVR_Undefine($$) +{ + my($hash, $name) = @_; + + Log3 $name, 5, "DENON_AVR $name: called Undefine."; + + delete $modules{DENON_AVR_ZONE}{defptr}{$name}{1} + if ( defined( $modules{DENON_AVR_ZONE}{defptr}{$name}{1} ) ); + + RemoveInternalTimer($hash); + DevIo_CloseDev($hash); + + DENON_AVR_RCdelete($name); + DENON_AVR_Delete_Zone($name."_Zone_2", "2"); + DENON_AVR_Delete_Zone($name."_Zone_3", "3"); + + return undef; +} + +############################# +sub +DENON_AVR_Get($@) +{ + my ($hash, @a) = @_; + my $name = $hash->{NAME}; + + return "argument is missing" if (int(@a) < 2 && int(@a) > 3); + + if ($a[1] =~ /^(power|volumeStraight|volume|mute|eco|display|input|disconnect|reconnect|remotecontrol|autoStandby|sound|statusRequest|mediaInfo|surroundMode|zone)$/) + { + if ($a[1] eq "statusRequest") + { + # Force update of status + return DENON_AVR_Command_StatusRequest($hash); + } + if ($a[1] eq "mediaInfo") + { + DENON_AVR_Write($hash, "NSE", "query"); + return undef; + } + elsif ($a[1] eq "remotecontrol") + { + return DENON_AVR_RCmake($name); + } + elsif ($a[1] eq "reconnect") + { + my $status = ReadingsVal( $name, "state", "opened" ); + if($status ne "opened") + { + DevIo_OpenDev($hash, 0, "DENON_AVR_DoInit"); + return "Try to initialize device!"; + } + else + { + return "Disconnect device first!"; + } + } + elsif ($a[1] eq "zone") + { + my $return = DENON_AVR_Make_Zone($name."_Zone_".$a[2], $a[2]); + DENON_AVR_Command_StatusRequest($hash); + return $return; + } + elsif ($a[1] eq "disconnect") + { + RemoveInternalTimer($hash); + DevIo_CloseDev($hash); + $hash->{STATE} = "disconnected"; + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "presence", "absent"); + readingsBulkUpdate($hash, "state", "disconnected"); + readingsBulkUpdate($hash, "stateAV", DENON_AVR_GetStateAV($hash)); + readingsEndUpdate($hash, 1); + Log3 $name, 5, "$name: closed."; + + return "Disconnected device!"; + } + elsif(defined(ReadingsVal( $name, $a[1], "" ))) + { + return ReadingsVal( $name, $a[1], "" ); + } + else + { + return "No such reading: $a[1]"; + } + } + else + { + my @inputs = (); + foreach my $key (sort(keys %{$DENON_db->{'SI'}})) { + my $device = $DENON_db->{'SI'}{$key}; + if ( defined($DENON_db->{'SS'}{'SOD'}{$device}) && $DENON_db->{'SS'}{'SOD'}{$device} eq 'USE' ) + { + push(@inputs, $key); + } + } + return "Unknown argument $a[1], choose one of power volumeStraight volume mute eco display input disconnect reconnect remotecontrol autoStandby sound statusRequest mediaInfo surroundMode zone:2,3,4"; + } +} + +################################### +sub +DENON_AVR_Set($@) +{ + my ($hash, @a) = @_; + my $name = $hash->{NAME}; + + my @channel = (); + my $favorites = AttrVal( $name, "favorites", 4 ); + my @favorite = (1..$favorites); + my $maxFavorites = AttrVal( $name, "maxFavorites", 20 ); + my @favoriteList = (1..$maxFavorites); + my @preset = (01..56); + my $maxPreset = AttrVal( $name, "maxPreset", 35 ); + my $presetMode = AttrVal( $name, "presetMode", "numeric" ); + my @presetCall = (00..$maxPreset); + my @presetCallAn = ("A1","A2","A3","A4","A5","A6","A7","A8","B1","B2","B3","B4","B5","B6","B7","B8","C1","C2","C3","C4","C5","C6","C7","C8","D1","D2","D3","D4","D5","D6","D7","D8","E1","E2","E3","E4","E5","E6","E7","E8","F1","F2","F3","F4","F5","F6","F7","F8","G1","G2","G3","G4","G5","G6","G7","G8"); + my @inputs = (); + my @inputSound = (); + my @usedInputs = (); + my @remoteControl = (); + my @resolution = (); + my @resolutionHDMI = (); + my @tuner = (); + my @multiEQ = (); + my @dynvol = (); + my $select = "quick"; + my $sliderSraight = "-80,0.5,18,1 "; + my $slider = "0,0.5,98,1 "; + my $streams = ""; + my $dezibel = AttrVal($name, "unit", "off") eq "on" ? " dB" : ""; + + + foreach my $key (sort(keys %{$DENON_db->{'CV'}})) { + push(@channel, $DENON_db->{'CV'}{$key}."_up"); + push(@channel, $DENON_db->{'CV'}{$key}."_down"); + } + + foreach my $key (sort(keys %{$DENON_db->{'REMOTE'}})) { + push(@remoteControl, $key); + } + + foreach my $key (sort(keys %{$DENON_db->{'VS'}{'SC'}})) { + push(@resolution, $DENON_db->{'VS'}{'SCH'}{$key}) if ($key ne "SC"); + } + + foreach my $key (sort(keys %{$DENON_db->{'VS'}{'SCH'}})) { + push(@resolutionHDMI, $DENON_db->{'VS'}{'SCH'}{$key}) if ($key ne "SCH"); + } + + if ( exists( $attr{$name}{inputs} ) ) { + @usedInputs = split(/,/,$attr{$name}{inputs}); + + foreach(@usedInputs) { + if ( defined($DENON_db->{'SI'}{$_})) { + push(@inputs, $_); + } + } + } + else + { + foreach my $key (sort(keys %{$DENON_db->{'SI'}})) { + my $device = $DENON_db->{'SI'}{$key}; + + if ( defined($DENON_db->{'SS'}{'SOD'}{$device})) + { + if ($DENON_db->{'SS'}{'SOD'}{$device} eq 'USE') + { + push(@inputs, $key); + } + push(@usedInputs, $key); + } + } + } + + foreach my $key (sort(keys %{$DENON_db->{'SD'}})) { + push(@inputSound, $DENON_db->{'SD'}{$key}); + } + + foreach my $key (sort(keys %{$DENON_db->{'TM'}{'AN'}})) { + push(@tuner, $key); + } + + foreach my $key (sort(keys %{$DENON_db->{'PS'}{'MULTEQ'}})) { + my $value = $DENON_db->{'PS'}{'MULTEQ'}{$key}; + push(@multiEQ, $value) if ($key ne "MULTEQ"); + } + + foreach my $key (sort(keys %{$DENON_db->{'PS'}{'DYNVOL'}})) { + my $value = $DENON_db->{'PS'}{'DYNVOL'}{$key}; + push(@dynvol, $value) if ($key ne "DYNVOL"); + } + + if(AttrVal($name, "brand", "Denon") eq "Marantz") + { + $select = "smart"; + } + + my $ceolEntry = ""; + if(AttrVal($name, "type", "AVR") eq "Ceol") + { + $sliderSraight = "-80,1,-20,1 "; + $slider = "0,1,60,1 "; + $ceolEntry = "clock" . " " . + "favorite_Memory:" . join(",", @favorite) . " " . + "favorite_Delete:" . join(",", @favorite) . " " . + "balance:slider,-6,1,6" . " " . + "sdb:on,off" . " " . + "sourceDirect:on,off" . " "; + } + else + { + $ceolEntry = "favoriteList:" . join(",", @favoriteList) . " "; + } + + my $usage = "Unknown argument $a[1], choose one of on off toggle volumeDown volumeUp mute:on,off,toggle muteT eco:on,auto,off allZoneStereo:on,off display:off,bright,dim,dark,toggle setup:on,off autoStandby:off,15min,30min,60min sleep:off,10min,15min,20min,30min,40min,50min,60min,70min,80min,90min,100min,110min,120min trigger1:on,off trigger2:on,off audysseyLFC:on,off cinemaEQ:on,off dynamicEQ:on,off loudness:on,off aspectRatio:4:3,16:9 monitorOut:auto,1,2 audioOutHDMI:amplifier,tv videoProcessingMode:auto,Game,Movie verticalStretch:on,off zoneMain:on,off " . + "volumeStraight:slider," . $sliderSraight . + "volume:slider," . $slider . + $select . "select:1,2,3,4,5 " . + "resolution:" . join(",", @resolution) . " " . + "resolutionHDMI:" . join(",", @resolutionHDMI) . " " . + "multiEQ:" . join(",", @multiEQ) . " " . + "dynamicVolume:" . join(",", @dynvol) . " " . + "lowFrequencyEffects:slider,-10,1,0 " . + "bass:slider,-6,1,6 treble:slider,-6,1,6 " . + "channelVolume:" . join(",", @channel) . ",FactoryDefaults" . " " . + "tuner:" . join(",", @tuner) . " " . + "tunerPreset:" . join(",", @preset) . " " . + "tunerPresetMemory:" . join(",", @preset) . " " . + "preset:1,2,3" . " " . + "presetCall:" . join(",", @presetCall) . " " . + "presetMemory:" . join(",", ($presetMode eq "alphanumeric" ? @presetCallAn : @presetCall)) . " " . + "favorite:" . join(",", @favorite) . " " . + $ceolEntry . + "input:" . join(",", @inputs) . " " . + "inputSound:" . join(",", @inputSound) . " " . + "usedInputs:multiple-strict," . join(",", @usedInputs) . " " . + "remoteControl:" . join(",", @remoteControl) . " " . + "surroundMode:" . join(",", sort keys %{$DENON_db->{'MS'}}) . " " . + "rawCommand"; + + if(AttrVal($name, "dlnaName", "") ne "") + { + $streams = DENON_AVR_GetStream("", "list"); + if ($streams ne "") + { + $usage .= " stream:" . $streams; + } + } + + if ($a[1] eq "?") + { + return $usage; + } + + readingsBeginUpdate($hash); + + if ($a[1] =~ /^(on|off)$/) + { + return DENON_AVR_Command_SetPower($hash, $a[1]); + } + elsif ($a[1] =~ "zoneMain") + { + DENON_AVR_Write($hash, "ZM".uc($a[2]), "zoneMain"); + readingsBulkUpdate($hash, "zoneMain", $a[2]); + readingsBulkUpdate($hash, "stateAV", DENON_AVR_GetStateAV($hash)); + } + elsif ($a[1] eq "quickselect") + { + DENON_AVR_Write($hash, "MSQUICK".$a[2], "quickselect"); + readingsBulkUpdate($hash, "quickselect", $a[2]); + } + elsif ($a[1] eq "smartselect") + { + DENON_AVR_Write($hash, "MSSMART".$a[2], "smartselect"); + readingsBulkUpdate($hash, "smartselect", $a[2]); + } + elsif ($a[1] eq "tuner") + { + my $tuner = DENON_GetValue('TM', 'AN', $a[2]); + DENON_AVR_Write($hash, "TMAN".$tuner, "tuner"); + } + elsif ($a[1] eq "tunerPreset") + { + my $preset = sprintf("%.2d", $a[2]); + DENON_AVR_Write($hash, "TPAN".$preset, "preset"); + } + elsif ($a[1] eq "tunerPresetMemory") + { + my $preset = sprintf("%.2d", $a[2]); + DENON_AVR_Write($hash, "TPANMEM".$preset, "tunerPresetMemory"); + } + elsif ($a[1] eq "preset") + { + my $preset = $a[2]; + DENON_AVR_Write($hash, "NSP".$preset, "tunerPreset"); + } + elsif ($a[1] eq "presetCall") + { + my $preset = sprintf("%.2d", $a[2]); + + DENON_AVR_Write($hash, "NSB".$preset, "presetCall"); + } + elsif ($a[1] eq "presetMemory") + { + if ($a[2] =~ /^[ABCDEFG][0-8]$/){ + DENON_AVR_Write($hash, "NSC".$a[2], "presetMemory"); + } + else { + my $preset = sprintf("%.2d", $a[2]); + DENON_AVR_Write($hash, "NSC".$preset, "presetMemory"); + } + } + elsif ($a[1] eq "favorite") + { + my $favorite = $a[2]; + if(AttrVal($name, "type", "AVR") eq "Ceol") + { + $favorite = sprintf ('%02d', $favorite); + DENON_AVR_Write($hash, "FV ".$favorite, "favorite"); + } + else + { + DENON_AVR_Write($hash, "ZMFAVORITE".$favorite, "favorite"); + } + readingsBulkUpdate($hash, "favorite", $favorite); + } + elsif ($a[1] eq "favorite_Memory") + { + my $favorite = $a[2]; + $favorite = sprintf ('%02d', $favorite); + DENON_AVR_Write($hash, "FVMEM ".$favorite, "favorite_Memory"); + } + elsif ($a[1] eq "favorite_Delete") + { + my $favorite = $a[2]; + $favorite = sprintf ('%02d', $favorite); + DENON_AVR_Write($hash, "FVDEL ".$favorite, "favorite_Delete"); + } + elsif ($a[1] eq "favoriteList") + { + my $fav = $a[2]; + DENON_AVR_SetFavorite($name, $fav); + } + elsif ($a[1] eq "toggle") + { + my $newPowerState = DENON_GetValue('SWITCH', ReadingsVal( $name, "state", "on")); + return DENON_AVR_Command_SetPower($hash, $newPowerState); + } + elsif ($a[1] eq "mute" || $a[1] eq "muteT") + { + my $mute = defined($a[2]) ? $a[2] : "?"; + if ($mute eq "toggle" || $a[1] eq "muteT") + { + $a[1] = "mute"; + my $newMuteState = DENON_GetValue('SWITCH', ReadingsVal( $name, $a[1], "off")); + return DENON_AVR_Command_SetMute($hash, $newMuteState); + } + else + { + return DENON_AVR_Command_SetMute($hash, $mute); + } + } + elsif ($a[1] eq "input") + { + my $input = DENON_GetValue('SI', $a[2]); + return DENON_AVR_Command_SetInput($hash, $input, $a[2]); + } + elsif ($a[1] eq "remoteControl") + { + if($a[2] =~ /^(up|down|left|right|enter)$/) + { + if(ReadingsVal( $name,"input", "" ) =~ /^(iRadio|Mediaplayer|OnlineMusic|Spotify|LastFM|Server|Favorites|SiriusXM|Bluetooth|Usb\/iPod|Usb_play|iPod_play|iRadio_play|Favorites_play|Pandora)$/) + { + if(ReadingsVal( $name, "setup", "off" ) eq "on") + { + my $remote = DENON_GetValue('MN', $a[2]); + DENON_AVR_Write($hash, "MN".$remote, "remoteControl"); + } + my $remote = DENON_GetValue('NS', $a[2]); + DENON_AVR_Write($hash, "NS".$remote, "remoteControl"); + } + elsif(ReadingsVal( $name,"input", "" ) =~ /^(Tuner)$/) + { + if(ReadingsVal( $name, "setup", "off" ) eq "on") + { + my $remote = DENON_GetValue('MN', $a[2]); + DENON_AVR_Write($hash, "MN".$remote, "remoteControl"); + } + + if(ReadingsVal( $name, "tunerBand", "?" ) =~ /^(AM|FM)$/) + { + if($a[2] eq "up") + { + DENON_AVR_Write($hash, "TPANUP", "remoteControl"); + } + elsif($a[2] eq "down") + { + DENON_AVR_Write($hash, "TPANDOWN", "remoteControl"); + } + elsif($a[2] eq "left") + { + DENON_AVR_Write($hash, "TFANDOWN", "remoteControl"); + } + elsif($a[2] eq "right") + { + DENON_AVR_Write($hash, "TFANUP", "remoteControl"); + } + } + } + else + { + my $remote = DENON_GetValue('MN', $a[2]); + DENON_AVR_Write($hash, "MN".$remote, "remoteControl"); + } + } + else + { + if($a[2] eq "eco") + { + my $state = ReadingsVal( $name, "eco", "off" ); + my $cmd = DENON_GetValue('TOGGLE', $state); + + DENON_AVR_Write($hash, 'ECO'.uc($cmd), "remoteControl"); + readingsBulkUpdate($hash, "eco", $cmd); + } + elsif($a[2] eq "setup") + { + my $state = DENON_GetValue('SWITCH', ReadingsVal( $name, "setup", "on" )); + DENON_AVR_Write($hash, 'MNMEN '.uc($state), "remoteControl"); + readingsBulkUpdate($hash, "setup", $state); + } + elsif($a[2] eq "allZoneStereo") + { + my $state = DENON_GetValue('SWITCH', ReadingsVal( $name, "allZoneStereo", "on" )); + DENON_AVR_Write($hash, 'MNZST '.uc($state), "remoteControl"); + readingsBulkUpdate($hash, "allZoneStereo", $state); + } + elsif ($a[2] eq "clock") + { + DENON_AVR_Write($hash, 'CLK', "clock"); + return undef; + } + elsif ($a[1] eq "sdb") + { + my $state = ReadingsVal( $name, "sdb", "off" ); + DENON_AVR_Write($hash, "SDB ".uc($state), "remoteControl"); + readingsBulkUpdate($hash, "sdb", $state); + } + elsif ($a[1] eq "sourceDirect") + { + my $state = ReadingsVal( $name, "sourceDirect", "off" ); + DENON_AVR_Write($hash, "SDI ".uc($state), "remoteControl"); + readingsBulkUpdate($hash, "sourceDirect", $state); + } + elsif($a[2] =~ /^in_(.+)/) #inputs + { + my $remote = DENON_GetValue('SI', $1); + DENON_AVR_Command_SetInput($hash, $remote, $1); + } + elsif($a[2] =~ /^sm_(.+)/) #sound-mode + { + my $remote = DENON_GetValue('MS', $1); + DENON_AVR_Write($hash, "MS".$remote, "remoteControl"); + } + elsif($a[2] =~ /^pc_(.+)/) #preset call 00-55 or 00-35 (AVR > 2014) + { + my $preset = sprintf("%.2d", $1); + DENON_AVR_Write($hash, "NSB".$preset, "presetCall"); + } + elsif($a[2] =~ /^pm_(.+)/) #preset memory call 00-55, 00-35 (AVR > 2014) or A1-G8 + { + if ($a[2] =~ /^[ABCDEFG][0-8]$/){ + DENON_AVR_Write($hash, "NSC".$a[2], "presetMemory"); + } + else { + my $preset = sprintf("%.2d", $a[2]); + DENON_AVR_Write($hash, "NSC".$preset, "presetMemory"); + } + } + elsif($a[2] =~ /^main_(on|off)/) #main zone + { + fhem("set $name zoneMain $1"); + } + else + { + if(exists $DENON_db->{'MN'}{$a[2]}) #system remote + { + my $remote = DENON_GetValue('MN', $a[2]); + DENON_AVR_Write($hash, "MN".$remote, "remoteControl"); + } + elsif(exists $DENON_db->{'NS'}{$a[2]}) #media remote + { + my $remote = DENON_GetValue('NS', $a[2]); + DENON_AVR_Write($hash, "NS".$remote, "remoteControl"); + readingsBulkUpdate($hash, "playStatus", 'paused') if($a[1] eq "pause"); + readingsBulkUpdate($hash, "playStatus", 'playing') if($a[1] eq "play"); + readingsBulkUpdate($hash, "playStatus", 'stopped') if($a[1] eq "stop"); + } + else + { + fhem("set $name $a[2]"); + } + } + } + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "surroundMode") + { + my $sound = $a[2]; + my $cmd = DENON_GetValue('MS', $a[2]); + DENON_AVR_Write($hash, "MS".$cmd, "surroundMode"); + + readingsBulkUpdate($hash, "surroundMode", $sound); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "volumeStraight") + { + my $volume = $a[2]; + return DENON_AVR_Command_SetVolume($hash, $volume + 80); + } + elsif ($a[1] eq "volume") + { + my $volume = $a[2]; + return DENON_AVR_Command_SetVolume($hash, $volume); + } + elsif ($a[1] eq "volumeDown") + { + my $cmd = "MVDOWN"; + my $volume = $a[2]; + if($a[2]) + { + $volume = $hash->{helper}{volume} - $volume; + return DENON_AVR_Command_SetVolume($hash, $volume); + } + else + { + DENON_AVR_Write($hash, $cmd, "volumeDown"); + } + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "volumeUp") + { + my $cmd = "MVUP"; + my $volume = $a[2]; + if($a[2]) + { + $volume = $hash->{helper}{volume} + $volume; + return DENON_AVR_Command_SetVolume($hash, $volume); + } + else + { + DENON_AVR_Write($hash, $cmd, "volumeUp"); + } + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "channelVolume") + { + my $channel = ""; + my $command = $a[2]; + my $volume = ""; + if($command =~ /^(.+)_(up|down)/) + { + $channel = DENON_GetKey("CV", $1); + $channel = $channel." ".uc($2); + $volume = uc($2); + } + elsif($command =~ /^FactoryDefaults/) + { + $channel = 'ZRL'; + $volume = "reset"; + } + else + { + $channel = DENON_GetKey("CV", $command); + $volume = $a[3] + 50; + if ($volume % 1 == 0) + { + $volume = 38 if($volume < 38); + $volume = 62 if($volume > 62); + $volume = sprintf ('%02d', $volume); + $channel = $channel." ".$volume; + } + elsif ($volume % 1 == 0.5) + { + $volume = 38.5 if($volume < 38.5); + $volume = 61.5 if($volume > 61.5); + $volume = sprintf ('%03d', ($volume * 10)); + $channel = $channel." ".$volume; + } + else + { + return undef; + } + } + DENON_AVR_Write($hash, "CV".$channel, $volume); + readingsEndUpdate($hash, 1); + DENON_AVR_Write($hash, "CV?", "query"); + return undef; + } + elsif ($a[1] eq "bass") + { + my $volume = $a[2] + 50; + DENON_AVR_Write($hash, "PSBAS ".$volume, "bass"); + readingsBulkUpdate($hash, "bass", $a[2].$dezibel); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "treble") + { + my $volume = $a[2] + 50; + DENON_AVR_Write($hash, "PSTRE ".$volume, "treble"); + readingsBulkUpdate($hash, "treble", $a[2].$dezibel); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "balance") + { + my $volume = $a[2] + 50; + DENON_AVR_Write($hash, "PSBAL ".$volume, "balance"); + readingsBulkUpdate($hash, "balance", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "eco") + { + my $cmd = DENON_GetValue("ECO", $a[2]); + DENON_AVR_Write($hash, "ECO".$cmd, "eco"); + readingsBulkUpdate($hash, "eco", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "setup") + { + DENON_AVR_Write($hash, 'MNMEN '.uc($a[2]), "setup"); + readingsBulkUpdate($hash, "setup", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif($a[1] eq "allZoneStereo") + { + DENON_AVR_Write($hash, 'MNZST '.uc($a[2]), "allZoneStereo"); + readingsBulkUpdate($hash, "allZoneStereo", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif($a[1] eq "cinemaEQ") + { + DENON_AVR_Write($hash, 'PSCINEMA EQ.'.uc($a[2]), "cinemaEQ"); + readingsBulkUpdate($hash, "cinemaEQ", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif($a[1] eq "multiEQ") + { + my $cmd = DENON_GetKey("PS", "MULTEQ", $a[2]); + DENON_AVR_Write($hash, 'PSMULTEQ:'.$cmd, "multiEQ"); + readingsBulkUpdate($hash, "multiEQ", $cmd); + readingsEndUpdate($hash, 1); + return undef; + } + elsif($a[1] eq "dynamicEQ") + { + DENON_AVR_Write($hash, 'PSDYNEQ '.uc($a[2]), "dynamicEQ"); + readingsBulkUpdate($hash, "dynamicEQ", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif($a[1] eq "dynamicVolume") + { + my $cmd = DENON_GetKey("PS", "DYNVOL", $a[2]); + DENON_AVR_Write($hash, "PSDYNVOL ".$cmd, "dynamicVolume"); + readingsBulkUpdate($hash, "dynamicVolume", $cmd); + readingsEndUpdate($hash, 1); + return undef; + } + elsif($a[1] eq "audysseyLFC") + { + DENON_AVR_Write($hash, 'PSLFC '.uc($a[2]), "audysseyLFC"); + readingsBulkUpdate($hash, "audysseyLFC", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif($a[1] eq "lowFrequencyEffects") + { + my $volume = sprintf ('%02d', $a[2]); + DENON_AVR_Write($hash, "PSLFE ".$volume, "lowFrequencyEffects"); + readingsBulkUpdate($hash, "lowFrequencyEffects", ($volume * -1).$dezibel); + readingsEndUpdate($hash, 1); + return undef; + } + elsif($a[1] eq "loudness") + { + DENON_AVR_Write($hash, "PSLOM ".uc($a[2]), "loudness"); + readingsBulkUpdate($hash, "loudness", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "clock") + { + DENON_AVR_Write($hash, 'CLK', "clock"); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "sdb") + { + DENON_AVR_Write($hash, "SDB ".uc($a[2]), "sdb"); + readingsBulkUpdate($hash, "sdb", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "sourceDirect") + { + DENON_AVR_Write($hash, "SDI ".uc($a[2]), "sdi"); + readingsBulkUpdate($hash, "sourceDirect", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "display") + { + my $cmd = DENON_GetValue('DIM', $a[2]); + DENON_AVR_Write($hash, 'DIM '.$cmd, "display"); + readingsBulkUpdate($hash, "display", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "autoStandby") + { + my $cmd = DENON_GetValue('STBY', $a[2]); + DENON_AVR_Write($hash, 'STBY'.$cmd, "autoStandby"); + readingsBulkUpdate($hash, "autoStandby", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "sleep") + { + my $cmd = DENON_GetValue('SLP', $a[2]); + DENON_AVR_Write($hash, 'SLP'.$cmd, "sleep"); + readingsBulkUpdate($hash, "sleep", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif($a[1] =~ /^(trigger1|trigger2)$/) + { + my $trigger = $a[1]; + $trigger =~ /^trigger([0-9])/; + DENON_AVR_Write($hash, 'TR'.$1." ".uc($a[2]), "trigger"); + readingsBulkUpdate($hash, "trigger".$1, $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "usedInputs") + { + DENON_AVR_SetUsedInputs($hash, $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "inputSound") + { + my $cmd = DENON_GetKey("SD", $a[2]); + DENON_AVR_Write($hash, "SD".$cmd, "inputSound"); + readingsBulkUpdate($hash, "inputSound", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "aspectRatio") + { + my $cmd = DENON_GetKey("VS", "ASP", $a[2]); + DENON_AVR_Write($hash, "VSASP".$cmd, "aspectRatio"); + readingsBulkUpdate($hash, "aspectRatio", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "monitorOut") + { + my $cmd = DENON_GetKey("VS", "MONI", $a[2]); + DENON_AVR_Write($hash, "VSMONI".$cmd, "monitorOut"); + readingsBulkUpdate($hash, "monitorOut", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "resolution") + { + my $cmd = DENON_GetKey("VS", "SC", $a[2]); + DENON_AVR_Write($hash, "VSSC".$cmd, "resolution"); + readingsBulkUpdate($hash, "resolution", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "resolutionHDMI") + { + my $cmd = DENON_GetKey("VS", "SCH", $a[2]); + DENON_AVR_Write($hash, "VSSCH".$cmd, "resolutionHDMI"); + readingsBulkUpdate($hash, "resolutionHDMI", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "audioOutHDMI") + { + my $cmd = DENON_GetKey("VS", "AUDIO", $a[2]); + DENON_AVR_Write($hash, "VSAUDIO".$cmd, "audioOutHDMI"); + readingsBulkUpdate($hash, "audioOutHDMI", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "videoProcessingMode") + { + my $cmd = DENON_GetKey("VS", "VPM", $a[2]); + DENON_AVR_Write($hash, "VSVPM".$cmd, "videoProcessingMode"); + readingsBulkUpdate($hash, "videoProcessingMode", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "verticalStretch") + { + DENON_AVR_Write($hash, "VSVST ".uc($a[2]), "verticalStretch"); + readingsBulkUpdate($hash, "verticalStretch", $a[2]); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "rawCommand") + { + my $cmd = $a[2]; + $cmd = $a[2]." ".$a[3] if defined $a[3]; + $cmd = $cmd." ".$a[4] if defined $a[4]; + DENON_AVR_Write($hash, $cmd, "rawCommand"); + readingsEndUpdate($hash, 1); + return undef; + } + elsif ($a[1] eq "stream") + { + my $cmd = $a[2]; + my $dlnaDevice = AttrVal( $name, "dlnaName", "" ); + my $dlnaStream = DENON_AVR_GetStream($cmd, "url"); + if($dlnaDevice ne "" && $dlnaStream ne "") + { + fhem("set $dlnaDevice stream $dlnaStream"); + readingsBulkUpdate($hash, "currentStream", $cmd); + readingsEndUpdate($hash, 1); + } + return undef; + } + else + { + if(exists $DENON_db->{'MN'}{$a[1]}) #system remote + { + my $remote = DENON_GetValue('MN', $a[1]); + DENON_AVR_Write($hash, "MN".$remote, "remoteControl"); + readingsEndUpdate($hash, 1); + return undef; + } + elsif(exists $DENON_db->{'NS'}{$a[1]}) #media remote + { + my $remote = DENON_GetValue('NS', $a[1]); + DENON_AVR_Write($hash, "NS".$remote, "remoteControl"); + readingsBulkUpdate($hash, "playStatus", 'paused') if($a[1] eq "pause"); + readingsBulkUpdate($hash, "playStatus", 'playing') if($a[1] eq "play"); + readingsBulkUpdate($hash, "playStatus", 'stopped') if($a[1] eq "stop"); + readingsEndUpdate($hash, 1); + return undef; + } + elsif($a[1] =~ /^main_(on|off)$/) + { + fhem("set $name zoneMain $1"); + } + else + { + readingsEndUpdate($hash, 1); + return $usage; + } + } +} + +##################################### +sub +DENON_AVR_Shutdown($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 5, "DENON_AVR $name: called Shutdown."; +} + +##################################### +sub +DENON_AVR_UpdateConfig($) +{ + # this routine is called 5 sec after the last define of a restart + # this gives FHEM sufficient time to fill in attributes + # it will also be called after each manual definition + # Purpose is to parse attributes and read config + my ($hash) = @_; + my $name = $hash->{NAME}; + + if (AttrVal($name, "webCmd", "na") eq "na") + { + $attr{$name}{webCmd} = "volume:mute:input:surroundMode"; + } + + DENON_AVR_Command_StatusRequest($hash); + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "presence", "present"); + + if ( defined($modules{DENON_AVR_ZONE}{defptr}{$name}{2}) || defined($modules{DENON_AVR_ZONE}{defptr}{$name}{3}) || defined($modules{DENON_AVR_ZONE}{defptr}{$name}{4})) + { + Log3 $name, 5, "DENON_AVR $name: Dispatching state change to slaves"; + Dispatch( $hash, "presence present", undef); + } + + if (ReadingsVal($name, "surroundMode", "na") eq "na") + { + readingsBulkUpdate($hash, "surroundMode", "Auto"); + } + if (ReadingsVal($name, "setup", "na") eq "na") + { + readingsBulkUpdate($hash, "setup", "off"); + } + if (ReadingsVal($name, "input", "na") eq "na") + { + $hash->{helper}{INPUT} = "Cbl/Sat"; + } + + my $deviceIP = $hash->{DeviceName}; + $deviceIP =~ /^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|[a-zA-Z0-9_-]+\.local):\d+$/; + $hash->{helper}{deviceIP} = $1; + + readingsBulkUpdate($hash, "playStatus", 'stopped'); + readingsEndUpdate($hash, 1); + + my $connectionCheck = AttrVal($name, "connectionCheck", "60"); + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + $connectionCheck, "DENON_AVR_ConnectionCheck", $hash, 0); + + Log3 $name, 5, "DENON_AVR $name: called UpdateConfig."; +} + +##################################### +sub +DENON_AVR_ConnectionCheck($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 5, "DENON_AVR $name: called ConnectionCheck."; + my $connectionCheck = AttrVal($name, "connectionCheck", "60"); + + if ($connectionCheck ne "off") { + + $hash->{STATE} = "opened"; + + RemoveInternalTimer($hash, "DENON_AVR_ConnectionCheck"); + + my $connState = DevIo_Expect( $hash, "PW?\r", $hash->{TIMEOUT} ); + + if ( defined($connState) ) { + # reset connectionCheck timer + my $checkInterval = AttrVal( $name, "connectionCheck", "60" ); + if ( $checkInterval ne "off" ) { + my $next = gettimeofday() + $checkInterval; + $hash->{helper}{nextConnectionCheck} = $next; + InternalTimer( $next, "DENON_AVR_ConnectionCheck", $hash, 0 ); + + my $avState = DENON_AVR_GetStateAV($hash); + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "stateAV", $avState) if(ReadingsVal( $name, "stateAV", "off") ne $avState); + readingsEndUpdate($hash, 1); + + Log3 $name, 5, "DENON_AVR_ConnectionCheck $name: reset internal timer."; + } + } + } +} + +##################################### +sub +DENON_AVR_Command_SetPower($$) +{ + my ($hash, $power) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 5, "DENON_AVR_Command $name: called SetPower"; + + my $status = DENON_GetValue('PW', lc($power)); + + DENON_AVR_Write($hash, 'PW'.$status, "power"); + + readingsBulkUpdate($hash, "power", lc($power)); + readingsBulkUpdate($hash, "stateAV", DENON_AVR_GetStateAV($hash)); + readingsEndUpdate($hash, 1); + + if($status eq "ON") + { + DENON_AVR_Write($hash, "ZM?", "query"); + } + + return undef; +} + +##################################### +sub +DENON_AVR_Command_SetMute($$) +{ + my ($hash, $mute) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 5, "DENON_AVR $name: called SetMute."; + + return "mute can only used when device is powered on" if (ReadingsVal( $name, "state", "off") eq "off"); + + my $status = DENON_GetValue('MU', lc($mute)); + + DENON_AVR_Write($hash, 'MU'.$status, "mute"); + + readingsBulkUpdate($hash, "stateAV", DENON_AVR_GetStateAV($hash)); + readingsEndUpdate($hash, 1); + + return undef; +} + +##################################### +sub +DENON_AVR_Command_SetInput($$$) +{ + my ($hash, $input, $friendlyName) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 5, "DENON_AVR $name: called SetInput."; + + DENON_AVR_Write($hash, "SI".$input, "input"); + readingsBulkUpdate($hash, "input", $friendlyName); + $hash->{helper}{INPUT} = $input; + + if ($input =~ /^(TUNER|DVD|BD|TV|SAT\/CBL|GAME|AUX1|AUX2|AUX3|AUX4|AUX5|AUX6|AUX7)$/) + { + for(my $i = 0; $i < 9; $i++) { + my $cur = ""; + if ($i == 2) + { + $cur = "s"; + my $status = DENON_GetValue('NSE', $i.$cur); + if($status ne 'ignore'){ + readingsBulkUpdate($hash, $status, '-'); + } + $cur = "a"; + } + my $status = DENON_GetValue('NSE', $i.$cur); + if($status ne 'ignore'){ + readingsBulkUpdate($hash, $status, '-'); + } + } + } + readingsEndUpdate($hash, 1); + return undef; +} + + +##################################### +sub +DENON_AVR_Command_SetVolume($$) +{ + my ($hash, $volume) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 5, "DENON_AVR $name: called SetVolume."; + + if(ReadingsVal( $name, "state", "off") eq "off") + { + return "Volume can only set when device is powered on!"; + } + else + { + $hash->{helper}{volume} = $volume; + if (($volume * 10) % 10 > 0) + { + $volume = sprintf ('%03d', ($volume * 10)); + } + else + { + $volume = sprintf ('%02d', $volume); + } + DENON_AVR_Write($hash, "MV".$volume, "volume"); + } + readingsEndUpdate($hash, 1); + return undef; +} + +##################################### +sub +DENON_AVR_Command_StatusRequest($) +{ + my ($hash) = @_; + + my $name = $hash->{NAME}; + + Log3 $name, 5, "DENON_AVR $name: called StatusRequest."; + + DENON_AVR_Write($hash, "PW?", "query"); #power + DENON_AVR_Write($hash, "MU?", "query"); #mute + DENON_AVR_Write($hash, "MV?", "query"); #mastervolume + DENON_AVR_Write($hash, "SI?", "query"); #input select + DENON_AVR_Write($hash, "MS?", "query"); #surround mode + DENON_AVR_Write($hash, "NSP", "query"); #presetP - older models(<=2013) + DENON_AVR_Write($hash, "ZM?", "query"); #main-zone + DENON_AVR_Write($hash, "Z2?", "query"); #zone2 + DENON_AVR_Write($hash, "Z3?", "query"); #zone3 + if(defined($modules{DENON_AVR_ZONE}{defptr}{$name}{4})) + { + DENON_AVR_Write($hash, "Z4?", "query"); + } + DENON_AVR_Write($hash, "SLP?", "query"); #sleep main-zone + DENON_AVR_Write($hash, "DIM ?", "query"); #dim display + DENON_AVR_Write($hash, "ECO?", "query"); #eco-mode + DENON_AVR_Write($hash, "STBY?", "query"); #standby + + DENON_AVR_Write($hash, "MSQUICK ?", "query"); #Quick select + DENON_AVR_Write($hash, "MSSMART ?", "query"); #Smart select (Marantz) + if(AttrVal($name, "type", "AVR") eq "Ceol") + { + DENON_AVR_Write($hash, "FV ?", "query"); #Favorite list (Ceol) + } + DENON_AVR_Write($hash, "MONI ?", "query"); #Monitor + DENON_AVR_Write($hash, "MNMEN?", "query"); #menu + DENON_AVR_Write($hash, "MNZST?", "query"); #All Zone Stereo + DENON_AVR_Write($hash, "NSE", "query"); #Onscreen Display Information List + DENON_AVR_Write($hash, "CV?", "query"); #channel volume +# DENON_AVR_Write($hash, "SR?", "query"); #record select - older models + DENON_AVR_Write($hash, "SD?", "query"); #sound input mode + DENON_AVR_Write($hash, "DC?", "query"); #digital input + DENON_AVR_Write($hash, "SV?", "query"); #video select mode + DENON_AVR_Write($hash, "TMAN?", "query"); #tuner + DENON_AVR_Write($hash, "TR?", "query"); #Trigger Control + DENON_AVR_Write($hash, "VSASP ?", "query"); #Aspect Ratio + DENON_AVR_Write($hash, "VSSC ?", "query"); #Resolution + DENON_AVR_Write($hash, "VSSCH ?", "query"); #Resolution (HDMI) + DENON_AVR_Write($hash, "VSVPM ?", "query"); #Video Processing Mode + DENON_AVR_Write($hash, "VSVST ?", "query"); #Vertical Stretch + DENON_AVR_Write($hash, "PSCINEMA EQ. ?", "query"); #CINEMA EQ + DENON_AVR_Write($hash, "PSLOM ?", "query"); #Loudness Management + DENON_AVR_Write($hash, "PSMULTEQ: ?", "query"); #MULT EQ + DENON_AVR_Write($hash, "PSDYNEQ ?", "query"); #DYNAMIC EQ + DENON_AVR_Write($hash, "PSDYNVOL ?", "query"); #Dynamic Volume + DENON_AVR_Write($hash, "PSLFC ?", "query"); #Audyssey LFC Status + + return "StatusRequest finished!"; +} + +##################################### +sub +DENON_AVR_GetStateAV($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + if ( ReadingsVal( $name, "presence", "absent" ) eq "absent" ) { + return "absent"; + } + elsif ( ReadingsVal( $name, "power", "off" ) eq "off" ) { + return "off"; + } + elsif ( ReadingsVal( $name, "mute", "off" ) eq "on" ) { + return "muted"; + } + elsif (ReadingsVal( $name, "playStatus", "stopped" ) ne "stopped") + { + return ReadingsVal( $name, "playStatus", "stopped" ); + } + elsif ( ReadingsVal( $name, "zoneMain", "off" ) eq "off" && ReadingsVal( $name, "power", "off" ) eq "on" ) { + return "mainOff"; + } + else { + return ReadingsVal( $name, "power", "off" ); + } +} + +sub +DENON_AVR_GetTimeStamp($) +{ + my ( $time ) = @_; + $time = $time * 1000; + return sprintf("%.0f", $time); +} + +sub +DENON_AVR_GetSpanPlaytime($$) { + my ( $time1, $time2 ) = @_; + my ($Ha, $Ma, $Sa, $Hb, $Mb, $Sb) = 0; + + if ($time1 =~ /^([0-9]{1,2}):([0-9]{2}):([0-9]{2})/) + { + $Ha = $1; + $Ma = $2; + $Sa = $3; + } + elsif ($time1 =~ /^([0-9]{1,2}):([0-9]{2})/) + { + $Ma = $1; + $Sa = $2; + } + + if ($time2 =~ /^([0-9]{1,2}):([0-9]{2}):([0-9]{2})/) + { + $Hb = $1; + $Mb = $2; + $Sb = $3; + } + elsif ($time2 =~ /^([0-9]{1,2}):([0-9]{2})/) + { + $Mb = $1; + $Sb = $2; + } + + my $timespan = (($Ha*3600) + ($Ma*60) + $Sa) - (($Hb*3600) + ($Mb*60) + $Sb); + + return $timespan; +} + +##################################### +sub +DENON_AVR_PlaytimeCheck ($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $playTime = AttrVal($name, "playTime", "off"); + + RemoveInternalTimer($hash, "DENON_AVR_PlaytimeCheck"); + + if ($playTime ne "off" && ReadingsVal($name, "playStatus", "stopped") ne "stopped") + { + InternalTimer(gettimeofday() + $playTime, "DENON_AVR_PlaytimeCheck", $hash, 0); + DENON_AVR_Write($hash, "NSE", "query"); + Log3 $name, 5, "DENON_AVR $name: called PlaytimeCheck"; + } + else + { + $hash->{helper}{isPause} = 0; + fhem("sleep 8;get $name mediaInfo"); + Log3 $name, 5, "DENON_AVR $name: called PlaytimeCheck mediaInfo"; + } +} + +##################################### +sub +DENON_AVR_PlaystatusCheck ($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $time = ReadingsVal( $name, "currentPlaytime", "na"); + my $oldtime = defined($hash->{helper}{playTime}) ? $hash->{helper}{playTime} : "00:00:00"; + my $status = ReadingsVal($name, "playStatus", "stopped"); + + my $timespan = 0; + if ($time ne "-") + { + $timespan = DENON_AVR_GetSpanPlaytime($time, $oldtime); + } + + + + if (ReadingsVal($name, "playStatus", "stopped") ne "stopped") + { + if ($timespan == 0) + { + readingsBulkUpdate($hash, "playStatus", "paused") if($status ne "paused"); + } + else + { + readingsBulkUpdate($hash, "playStatus", "playing") if($status ne "playing"); + } + + if ($time eq "-") + { + readingsBulkUpdate($hash, "playStatus", "stopped"); + } + } + else + { + if ($hash->{helper}{playTimeCheck} == 0) + { + readingsBulkUpdate($hash, "playStatus", 'stopped') if($status ne "stopped"); + } + else + { + readingsBulkUpdate($hash, "playStatus", 'playing') if($status ne "playing"); + readingsBulkUpdate($hash, "currentPlaytime", '0:00'); + } + } + + if ($hash->{helper}{playTimeCheck} == 1) + { + $hash->{helper}{playTime} = $time if ($time ne "-"); + } +} + +##################################### +sub +DENON_AVR_GetStream($$) { + my ( $name, $mode ) = @_; + + my $file = "./FHEM/Denon.streams"; + my $return = ""; + open( my $handle, "<", $file ) || return $return; + while(<$handle>){ + $_ =~ /^(.+)<>(.+)/; + if ($mode eq "url") + { + if($1 eq $name) + { + $return = $2; + last; + } + } + elsif ($mode eq "list") + { + $return .= $1 . ","; + } + } + close( $handle ); + + if ($mode eq "list") + { + chop($return); + } + Log3 $name, 5, "DENON_AVR $name GetStream: $return"; + return $return; +} + +##################################### +sub +DENON_AVR_SetFavorite($$) { + my ( $name, $fav ) = @_; + + fhem("set $name input Favorites"); + + my $sleep = AttrVal($name, "sleep", 5); + my $command = "sleep $sleep;"; + + if ($fav > 1) + { + for(my $i = 1; $i < $fav; $i++) { + $command .= "set $name remoteControl down;sleep 0.5;"; + } + } + $command .= "set $name remoteControl play"; + + fhem("$command"); + + Log3 $name, 5, "DENON_AVR $name SetFavorite: $command"; + + return; +} + +##################################### +sub +DENON_AVR_SetUsedInputs($$) { + my ($hash, $usedInputs) = @_; + my $name = $hash->{NAME}; + my @inputs = split(/,/,$usedInputs); + my @denonInputs = (); + + foreach (@inputs) + { + if(exists $DENON_db->{'SI'}{$_}) + { + push(@denonInputs, $_); + } + } + $attr{$name}{inputs} = join(",", @denonInputs); +} + +##################################### +sub +DENON_AVR_Make_Zone($$) { + my ( $name, $zone ) = @_; + if(!defined($defs{$name})) + { + fhem("define $name Denon_AVR_ZONE $zone"); + } + + Log3 $name, 3, "DENON_AVR $name: create Denon_AVR_ZONE $zone."; + return "Denon_AVR_ZONE $name created by DENON_AVR"; +} + +##################################### +sub +DENON_AVR_Delete_Zone($$) { + my ( $name, $zone ) = @_; + if(defined($defs{$name})) + { + fhem("delete $name"); + } + + Log3 $name, 3, "DENON_AVR $name: delete Denon_AVR_ZONE $zone."; + return "Denon_AVR_ZONE $name deleted by DENON_AVR"; +} + +##################################### +sub +DENON_AVR_RCmakenotify($$) { + my ( $name, $ndev ) = @_; + my $nname = "notify_$name"; + + fhem( "define $nname notify $name set $ndev remoteControl " . '$EVENT', 1 ); + Log3 $name, 3, "DENON_AVR $name: create notify for remoteControl."; + return "Notify created by DENON_AVR $nname"; +} + +##################################### +sub +DENON_AVR_RCmake($) { + my ( $name ) = @_; + if(!defined($defs{"Denon_AVR_RC_" . $name})) + { + fhem("define Denon_AVR_RC_$name remotecontrol"); + fhem("sleep 1;set Denon_AVR_RC_$name layout DENON_AVR_RC"); + + if(!defined($defs{"notify_Denon_AVR_RC_" . $name})) + { + fhem("sleep 1;set Denon_AVR_RC_$name makenotify $name"); + } + } + + Log3 $name, 3, "DENON_AVR $name: create remoteControl."; + return "Remotecontrol created by DENON_AVR $name"; +} + +##################################### +sub +DENON_AVR_RCdelete($) { + my ( $name ) = @_; + if(defined($defs{"Denon_AVR_RC_" . $name})) + { + fhem("delete Denon_AVR_RC_" . $name); + + if(defined($defs{"notify_Denon_AVR_RC_" . $name})) + { + fhem("sleep 1;delete notify_Denon_AVR_RC_$name"); + } + } + + Log3 undef, 3, "DENON_AVR $name: delete remoteControl."; + return "Remotecontrol deleted by DENON_AVR: $name"; +} + +##################################### +sub DENON_AVR_RClayout() { + my @row; + + $row[0] = "info:INFO,:blank,up:CHUP,:blank,option:OPTION,:blank,volumeUp:VOLUP,:blank,sm_Movie:MOVIE,sm_Music:MUSIC,sm_Game:GAME,sm_Pure:PURE,:blank,play:PLAY,:blank,toggle:POWEROFF3"; + $row[1] = ":blank,left:LEFT,enter:ENTER3,right:RIGHT,:blank,:blank,muteT:MUTE,:blank,in_Cbl/Sat:CBLSAT,in_Blu-Ray:BR,in_DVD:DVD,in_CD:CD,:blank,pause:PAUSE"; + $row[2] = "return:RETURN,:blank,down:CHDOWN,:blank,setup:SETUP,:blank,volumeDown:VOLDOWN,:blank,in_Mediaplayer:MEDIAPLAYER,in_iRadio:IRADIO,in_OnlineMusic:ONLINEMUSIC,in_Usb/iPod:IPODUSB,:blank,stop:STOP,:blank,eco:ECO"; + $row[3] = "attr rc_iconpath icons/remotecontrol"; + $row[4] = "attr rc_iconprefix black_btn_"; + + return @row; +} + +1; + + +=pod +=item device +=item summary control for DENON (Marantz) AV receivers via network or serial connection +=item summary_DE Steuerung von DENON (Marantz) AV Receivern per Netzwerk oder RS-232 +=begin html + + +

+ +

+

+ DENON_AVR +

+
    + Define +
      + define <name> DENON_AVR <ip-address-or-hostname[:PORT]>
      + define <name> DENON_AVR <devicename[@baudrate]>
      +
      + This module controls DENON (Marantz) A/V receivers in real-time via network connection.
      +
      + Instead of IP address or hostname you may set a serial connection format for direct connectivity.
      +
      + Example:
      +
      +
        + + define avr DENON_AVR 192.168.0.10
        +
        + # With explicit port
        + define avr DENON_AVR 192.168.0.10:23
        +
        + # With serial connection
        + define avr DENON_AVR /dev/ttyUSB0@9600 +
        +
      +

    +
    + Set +
      + set <name> <command> [<parameter>]
      +
      + Currently, the following commands are defined:
      +
        +
      • + allZoneStereo   -   set allZoneStereo on/off +
      • +
      • + audysseyLFC   -   set audysseyLFC on/off +
      • +
      • + autoStandby   -   set auto standby (off, 15,30,60 min) +
      • +
      • + bass   -   adjust bass level +
      • +
      • + channelVolume   -   adjust channel volume level for active speakers (up/down),
        +  to reset all channel level use FactoryDefaults
        +  Example to adjust volume level via command line: set avr channelVolume FrontLeft -1
        +  (possible values -12 to 12) +
      • +
      • + cinemaEQ   -   set cinemaEQ on/off +
      • +
      • + display   -   controls display dim state +
      • +
      • + dynamicEQ   -   set dynamicEQ on/off +
      • +
      • + dynamicVolume   -   set dynamicEQ off/light/medium/heavy +
      • +
      • + eco   -   controls eco state +
      • +
      • + favorite   -   switches between favorite (only older models) +
      • +
      • + favoriteList   -   select entries in favorite list (workaround for new models >= 2014),
        +  it is recommended to use set stream in combination with module 98_DLNARenderer! +
      • +
      • + input   -   switches between inputs +
      • +
      • + loudness   -   set loudness on/off +
      • +
      • + lowFrequencyEffect   -   adjust LFE level (-10 to 0) +
      • +
      • + multiEQ   -   set multiEQ off/reference/bypassLR... +
      • +
      • + mute on,off   -   controls volume mute +
      • +
      • + muteT   -   toggle mute state +
      • +
      • + off   -   turns the device in standby mode +
      • +
      • + on   -   powers on the device +
      • +
      • + preset   -   switches between presets (1-3, only older models) +
      • +
      • + presetCall   -   switches between presets (00-35, 00-55 older models) +
      • +
      • + presetMemory   -   save presets (00-35, 00-55 older models, for alphanumeric mode A1-G8 set attribute "presetMode" to "alphanumeric") +
      • +
      • + quickselect   -   switches between quick select modes (1-5, only new models) +
      • +
      • + rawCommand   -   send raw command to AV receiver +
      • +
      • + reconnect   -   reconnect AV receiver +
      • +
      • + remoteControl   -   remote commands (play, stop, pause,...) +
      • +
      • + setup   -   onscreen setup on/off +
      • +
      • + sleep   -   set sleep timer (off/10 to 120 min) +
      • +
      • + smartselect   -   switches between smart select modes (1-5, only Marantz, to activate set attribute brand to Marantz) +
      • +
      • + stream   -   send stream adress via module 98_DLNARenderer to reciever; the user has to create a file "Denon.streams" in folder "fhem/FHEM"
        +
        list format:
        + + Rockantenne<>http://mp3channels.webradio.antenne.de/rockantenne
        + Bayern3<>http://streams.br.de/bayern3_2.m3u
        + JamFM<>http://www.jam.fm/streams/jam-nmr-mp3.m3u
        +
        +
        The attribut "dlnaName" must be set to the name of the reciever in DLNARenderer module.
        +
      • +
      • + surroundMode   -   set surround mode +
      • +
      • + toggle   -   switch between on and off +
      • +
      • + treble   -   adjust treble level +
      • +
      • + trigger1   -   set trigger1 on/off +
      • +
      • + trigger2   -   set trigger2 on/off +
      • +
      • + tuner   -   switch between AM and FM +
      • +
      • + tunerPreset   -   switches between tuner presets (1-56) +
      • +
      • + tunerPresetMemory   -   save tuner presets (1-56) +
      • +
      • + usedInputs   -   set used inputs manually if needed (e.g. us model) +
      • +
      • + volume 0...98   -   set the volume level in percentage +
      • +
      • + volumeStraight -80...18   -   set the volume level in dB +
      • +
      • + volumeUp   -   increases the volume level +
      • +
      • + volumeDown   -   decreases the volume level +
      • +
      +

    +
    + Get +
      + get <name> <what>
      +
      + Currently, the following commands are defined:
      +
        +
      • + disconnect   -   disconnect AV receiver +
      • +
      • + mediaInfo   -   refresh current media infos +
      • +
      • + reconnect   -   reconnect AV receiver +
      • +
      • + remoteControl   -   autocreate remote ccontrol +
      • +
      • + statusRequest   -   refresh status +
      • +
      • + some readings   -   see list below +
      • +
      • + zone   -   autocreate zones +
      • +
      +
      + Generated Readings/Events:
      +
      + The AV reciever sends some readings only if settings (e.g. ampAssign) have changed
      + or the reciever has been disconnected (power supply) for more than 5 min and connected again.
      +
      +
        +
      • + ampAssign   -   amplifier settings for AV receiver (5.1, 7.1, 9.1,...) +
      • +
      • + autoStandby   -   auto standby state +
      • +
      • + bass   -   bass level in dB +
      • +
      • + currentAlbum   -   current album (mediaplayer, online music,...) +
      • +
      • + currentArtist   -   current artist (mediaplayer, online music,...) +
      • +
      • + currentBitrate   -   current bitrate (mediaplayer, online music,...) +
      • +
      • + currentMedia   -   current media (mediaplayer, online music,...) +
      • +
      • + currentStation   -   current station (online radio) +
      • +
      • + currentTitle   -   current title (mediaplayer, online music,...) +
      • +
      • + display   -   dim state of display +
      • +
      • + dynamicCompression   -   dynamic compression state +
      • +
      • + eco   -   eco state +
      • +
      • + input   -   selected input +
      • +
      • + level[speaker]   -   [speaker] level in dB (e.g. levelFrontRight) +
      • +
      • + lowFrequencyEffects   -   low frequency effects (LFE) state +
      • +
      • + mute   -   mute state +
      • +
      • + playStatus   -   current playStatus (playing/paused/stopped) +
      • +
      • + power   -   power state +
      • +
      • + samplingRate   -   sampling rate +
      • +
      • + signal   -   input signal sound +
      • +
      • + sound   -   actual sound type +
      • +
      • + state   -   state of AV reciever (on,off,disconnected) +
      • +
      • + stateAV   -   state of AV reciever (on,off,absent,mute) +
      • +
      • + surroundMode   -   actual surround mode (Auto, Stereo, Music,...) +
      • +
      • + toneControl   -   tone control state +
      • +
      • + treble   -   treble level in dB +
      • +
      • + tuner[Information]   -   tuner settings [Band, Frequency, Mode, Preset] +
      • +
      • + videoSelect   -   actual video select mode +
      • +
      • + volume   -   actual volume +
      • +
      • + volumeMax   -   actual maximum volume +
      • +
      • + volumeStraight   -   actual volume straight +
      • +
      • + zone   -   state of aviable zones (on/off) +
      • +
      +
      + Attributes
      +
      +
        +
      • + brand   -   brand of AV receiver (Denon/Marantz) - to activate smartselect set attribute brand to Marantz +
      • +
      • + connectionCheck   -   time to next connection check +
      • +
      • + disable   -   defined device on/off +
      • +
      • + dlnaName   -   name of Reciever in DLNARenderer module +
      • +
      • + favorites   -   max entries for favorites +
      • +
      • + maxFavorites   -   max entries in favorite list for "set favoriteList" +
      • +
      • + maxPreset   -   max entries in preset list +
      • +
      • + playTime   -   timespan to next playtime check (off, 1-60 sec) +
      • +
      • + presetMode   -   preset list mode (numeric [00-55] or alphanumeric [A1-G8]) +
      • +
      • + sleep   -   break between commands for use with "set favoriteList" +
      • +
      • + timeout   -   number of connection attempts +
      • +
      • + type   -   reciever type (AVR oder Ceol), steps for volumeslider (0.5 or 1) +
      • +
      • + unit   -   de-/activate units for readings (on or off) +
      • +
      +
    +
+ +=end html + + +=begin html_DE + + +

+ +

+

+ DENON_AVR +

+
    + Define +
      + define <name> DENON_AVR <ip-address-or-hostname[:PORT]>
      + define <name> DENON_AVR <devicename[@baudrate]>
      +
      + Dieses Modul steuert DENON (Marantz) A/V-Receiver über das Netzwerk.
      +
      + Anstatt der IP-Addresse oder dem Hostnamen kann eine serielle Schnittstelle angegeben werden.
      +
      + Beispiele:
      +
      +
        + + define avr DENON_AVR 192.168.0.10
        +
        + # Unter Angabe eines bestimmten Ports
        + define avr DENON_AVR 192.168.0.10:23
        +
        + # Mit serieller Schnittstelle
        + define avr DENON_AVR /dev/ttyUSB0@9600 +
        +
      +

    +
    + Set +
      + set <name> <command> [<parameter>]
      +
      + Momentan sind folgende Befehle verfügbar:
      +
        + +
      • + allZoneStereo   -   allZoneStereo an/aus +
      • +
      • + audysseyLFC   -   audysseyLFC an/aus +
      • +
      • + autoStandby   -   Zeit für den Auto-Standby setzen +
      • +
      • + bass   -   Bass-Pegel einstellen +
      • +
      • + channelVolume   -   Lautstärkepegel der aktiven Lautsprecher schrittweise setzen (up/down)
        +  Um alle Einstellungen zurückzusetzen, kann FactoryDefaults verwendet werden.
        +  Beispiel, wie der Lautstärkepegel direkt über die Kommandozeile gesetzt wird: set avr channelVolume FrontLeft -1
        +  (mögliche Werte sind -12 bis 12) +
      • +
      • + cinemaEQ   -   cinemaEQ an/aus +
      • +
      • + display   -   zur Auswahl der "Dim-Modi" des Displays +
      • +
      • + dynamicEQ   -   dynamicEQ an/aus +
      • +
      • + dynamicVolume   -   Wert für dynamicEQ setzen (off/light/medium/heavy) +
      • +
      • + eco   -   zur Auswahl des Eco-Modus +
      • +
      • + favorite   -   zur Auswahl der Favoriten (nur alte Modelle) +
      • +
      • + favoriteList   -   zur Auswahl der Favoriten aus der Listenansicht (Workaround für Modelle >= 2014)
        +  Es wird emfohlen den Set-Befehl stream in Kombination mit dem Modul 98_DLNARenderer zu verwenden! +
      • +
      • + input   -   zur Auswahl der Eingänge +
      • +
      • + loudness   -   Loudness an/aus +
      • +
      • + lowFrequencyEffect   -   LFE-Pegel einstellen (-10 to 0) +
      • +
      • + multiEQ   -   Wert für multiEQ setzen (off/reference/bypassLR...) +
      • +
      • + mute on,off   -   AV-Receiver laut/stumm schalten +
      • +
      • + muteT   -   zwischen laut und stumm wechseln +
      • +
      • + off   -   Standby AV-Receiver +
      • +
      • + on   -   AV-Receiver anschalten +
      • +
      • + preset   -   zwischen Voreinstellungen (1-3, nur alte Modelle) wechseln +
      • +
      • + presetCall   -   zwischen Voreinstellungen (00-35, 00-55 alte Modelle) wechseln +
      • +
      • + presetMemory   -   Voreinstellungen speichern (00-35, 00-55 alte Modelle, für Aktivierung der alphanumerschen Liste A1-G8 das Attribut presetMode auf alphanumeric setzen) +
      • +
      • + quickselect   -   zur Auswahl der "Quick-Select" Modi (1-5, nur neue Modelle) +
      • +
      • + rawCommand   -   schickt ein "raw command" zum AV-Receiver +
      • +
      • + reconnect   -   stellt die Verbindung zum AV-Receivers wieder her +
      • +
      • + remoteControl   -   Fernbedienungsbefehle (play, stop, pause,...) +
      • +
      • + setup   -   Anzeige Onscreen-Setup an/aus +
      • +
      • + sleep   -   Sleep-Timer (aus/10 bis 120 min) +
      • +
      • + smartselect   -   Smart-Select Modus wählen (1-5, nur Marantz, für Aktivierung das Attribut brand auf Marantz setzen) +
      • +
      • + stream   -   Aufruf von Streams über Modul 98_DLNARenderer; eine Datei "Denon.streams" mit einer Liste von Streams muss im Ordner "fhem/FHEM" selbst angelegt werden.
        +
        Format der Liste:
        + + Rockantenne<>http://mp3channels.webradio.antenne.de/rockantenne
        + Bayern3<>http://streams.br.de/bayern3_2.m3u
        + JamFM<>http://www.jam.fm/streams/jam-nmr-mp3.m3u
        +
        +
        Der Name des Recievers aus dem DLNARenderer-Modul muss als Attribut "dlnaName" im Denon-Modul gesetzt werden.
        +
      • +
      • + surroundMode   -   zur Auswahl der Surround-Modi +
      • +
      • + toggle   -   AV-Receiver an/aus +
      • +
      • + treble   -   Höhen-Pegel einstellen +
      • +
      • + trigger1   -   trigger1 an/aus +
      • +
      • + trigger2   -   trigger2 an/aus +
      • +
      • + tuner   -   zwischen AM und FM wechseln +
      • +
      • + tunerPreset   -   zwischen Radio Voreinstellungen (1-56) wechseln +
      • +
      • + tunerPresetMemory   -   Radio Voreinstellungen (1-56) speichern +
      • +
      • + usedInputs   -   zur manuellen Auswahl der genutzten Eingänge (z.B. AUX-Ausgänge hinzufügen/enfernen) +
      • +
      • + volume 0...98   -   Lautstärke in Prozent +
      • +
      • + volumeStraight -80...18   -   absolute Lautstärke in dB +
      • +
      • + volumeUp   -   erhöht Lautstärke +
      • +
      • + volumeDown   -   verringert Lautstärke +
      • +
      +

    +
    + Get +
      + get <name> <what>
      +
      + Momentan sind folgende Befehle verfügbar:
      +
        +
      • + disconnect   -   Verbindung zum AV receiver trennen +
      • +
      • + mediaInfo   -   aktuelle Medieninformationen abrufen +
      • +
      • + reconnect   -   Verbindung zum AV receiver wiederherstellen +
      • +
      • + remoteControl   -   Fernbedienung automatisch erzeugen lassen +
      • +
      • + statusRequest   -   Status neu laden +
      • +
      • + diverse Readings   -   siehe Liste unten +
      • +
      • + zone   -   Zonen automatisch erzeugen lassen +
      • +
      +
      + Erzeugte Readings/Events:
      +
      + Einige Statusmeldungen werden vom AV-Reciever nur gesendet, wenn die entsprechende
      + Einstellung (z.B. ampAssign) geändert wird oder der Reciever wieder ans Stromnetz
      + angeschlossen wird (muss ca. 5 min vom Strom getrennt sein!).
      +
      +
        +
      • + ampAssign   -   Endstufenzuweisung des AV-Receiver (5.1, 7.1, 9.1,...) +
      • +
      • + autoStandby   -   Standbyzustand des AV-Recievers +
      • +
      • + bass   -   Bass-Level in dB +
      • +
      • + currentAlbum   -   aktuelles Album (Mediaplayer, Online Music,...) +
      • +
      • + currentArtist   -   aktueller Künstler (Mediaplayer, Online Music,...) +
      • +
      • + currentBitrate   -   aktuelle Bitrate (Mediaplayer, Online Music,...) +
      • +
      • + currentMedia   -   aktuelle Quelle (Mediaplayer, Online Music,...) +
      • +
      • + currentStation   -   aktueller Sender (Online Radio) +
      • +
      • + currentTitle   -   aktueller Titel (Mediaplayer, Online Music,...) +
      • +
      • + display   -   Dim-Status des Displays +
      • +
      • + dynamicCompression   -   Status der dynamischen Kompression +
      • +
      • + eco   -   Eco-Status +
      • +
      • + input   -   gewählte Eingangsquelle +
      • +
      • + levelFrontLeft   -   Pegel des linken Frontlautsprechers in dB +
      • +
      • + levelFrontRight   -   Pegel des rechten Frontlautsprechers in dB +
      • +
      • + lowFrequencyEffects   -   LFE-Status (low frequency effect) +
      • +
      • + mute   -   Status der Stummschaltung +
      • +
      • + power   -   Einschaltzustand des AV-Recievers +
      • +
      • + samplingRate   -   aktuelle Sampling-Rate +
      • +
      • + signal   -   aktuell anliegendes Eingangssignal +
      • +
      • + sound   -   aktueller Sound-Modus +
      • +
      • + state   -   Status des AV-Recievers (on,off,disconnected) +
      • +
      • + stateAV   -   stateAV-Status des AV-Recievers (on,off,mute,absent) +
      • +
      • + surroundMode   -   gewählter Surround-Modus (Auto, Stereo, Music,...) +
      • +
      • + toneControl   -   Status der Klangkontrolle +
      • +
      • + treble   -   Höhen-Level in dB +
      • +
      • + videoSelect   -   gewählter Videoselect-Modus +
      • +
      • + volume   -   aktuelle Lautstärke in Prozent +
      • +
      • + volumeMax   -   maximale Lautstärke in Prozent +
      • +
      • + volumeStraight   -   aktuelle absolute Lautstärke in dB +
      • +
      +
      + Attribute
      +
      +
        +
      • + brand   -   Marke des Recievers (Denon oder Marantz) - um smartselect zu aktivieren Attribut brand auf Marantz setzen +
      • +
      • + connectionCheck   -   Zeitintervall für die Übeprüfung der Verbindung +
      • +
      • + disable   -   definiertes Gerät vorübergehend deaktivieren +
      • +
      • + dlnaName   -   Name des Recievers im DLNARenderer-Modul +
      • +
      • + favorites   -   maximale Anzahl der Favoriten +
      • +
      • + maxFavorites   -   maximale Anzahl der Einträge in der Favoritenliste für die Verwendung mit "set favoriteList" +
      • +
      • + maxPreset   -   maximale Anzahl der Einträge in der Liste für die Voreinstellungen +
      • +
      • + playTime   -   Zeitintervall für die Abfrage der Spielzeit bei Online-Medien (off, 1-60 sec) +
      • +
      • + presetMode   -   Verhalten der Liste für die Voreinstellungen (numerisch [00-55] oder alphanumerisch [A1-G8]) +
      • +
      • + sleep   -   Pause zwischen den Befehlen zum Aufruf der Favoritenliste mit "set favoriteList" +
      • +
      • + timeout   -   Anzahl der Versuch für den Verbindungsaufbau +
      • +
      • + type   -   Verstärkertyp (AVR oder Ceol), legt Schrittweite der Lautstärkeslider fest (0.5 oder 1) +
      • +
      • + unit   -   Einheiten für Readings de-/aktivieren (on oder off) +
      • +
      +
    +
+ + +=end html_DE + +=cut diff --git a/FHEM/71_DENON_AVR_ZONE.pm b/FHEM/71_DENON_AVR_ZONE.pm new file mode 100644 index 000000000..326e89558 --- /dev/null +++ b/FHEM/71_DENON_AVR_ZONE.pm @@ -0,0 +1,1441 @@ +############################################################################### +# 71_DENON_AVR_ZONE +# +# This file is part of Fhem. +# +# Fhem is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Fhem is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Fhem. If not, see . +# +############################################################################### +# +# DENON_AVR_ZONE maintained by Martin Gutenbrunner +# original credits to raman and the community (see Forum thread) +# +# This module enables FHEM to interact with Denon and Marantz audio devices. +# +# Discussed in FHEM Forum: https://forum.fhem.de/index.php/topic,58452.300.html +# +# $Id$ +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); + +sub DENON_AVR_ZONE_Get($$$); +sub DENON_AVR_ZONE_Set($$$); +sub DENON_AVR_ZONE_Attr($@); +sub DENON_AVR_ZONE_Define($$$); +sub DENON_AVR_ZONE_Undefine($$); + + +# Database and call-functions +###################################################################### +my $DENON_db_zone = { + 'UP' => 'up', + 'DOWN' => 'down', + 'ON' => 'on', + 'OFF' => 'off', + 'PHONO' => 'Phono', + 'CD' => 'CD', + 'TUNER' => 'Tuner', + 'DVD' => 'DVD', + 'BD' => 'Blu-Ray', + 'TV' => 'TV', + 'SAT/CBL' => 'Cbl/Sat', + 'MPLAY' => 'Mediaplayer', + 'GAME' => 'Game', + 'HDRADIO' => 'HDRadio', + 'NET' => 'OnlineMusic', + 'SPOTIFY' => 'Spotify', + 'LASTFM' => 'LastFM', + 'FLICKR' => 'Flickr', + 'IRADIO' => 'iRadio', + 'SERVER' => 'Server', + 'FAVORITES' => 'Favorites', + 'PANDORA' => 'Pandora', + 'SIRIUSXM' => 'SiriusXM', + 'AUX1' => 'Aux1', + 'AUX2' => 'Aux2', + 'AUX3' => 'Aux3', + 'AUX4' => 'Aux4', + 'AUX5' => 'Aux5', + 'AUX6' => 'Aux6', + 'AUX7' => 'Aux7', + 'BT' => 'Bluetooth', + 'USB/IPOD' => 'Usb/iPod', + 'USB' => 'Usb_play', + 'IPD' => 'iPod_play', + 'IRP' => 'iRadio_play', + 'FVP' => 'Favorites_play', + 'QUICK1' => 'Quick1', + 'QUICK2' => 'Quick2', + 'QUICK3' => 'Quick2', + 'QUICK4' => 'Quick4', + 'QUICK5' => 'Quick5', + 'SMART1' => 'Smart1', + 'SMART2' => 'Smart2', + 'SMART3' => 'Smart3', + 'SMART4' => 'Smart4', + 'SMART5' => 'Smart5', + 'SI' => { + 'Phono' => 'PHONO', + 'CD' => 'CD', + 'Tuner' => 'TUNER', + 'DVD' => 'DVD', + 'Blu-Ray' => 'BD', + 'TV' => 'TV', + 'Cbl/Sat' => 'SAT/CBL', + 'Mediaplayer' => 'MPLAY', + 'Game' => 'GAME', + 'HDRadio' => 'HDRADIO', + 'OnlineMusic' => 'NET', + 'Spotify' => 'SPOTIFY', + 'LastFM' => 'LASTFM', + 'Flickr' => 'FLICKR', + 'iRadio' => 'IRADIO', + 'Server' => 'SERVER', + 'Favorites' => 'FAVORITES', + 'Pandora' => 'PANDORA', + 'SiriusXM' => 'SIRIUSXM', + 'Aux1' => 'AUX1', + 'Aux2' => 'AUX2', + 'Aux3' => 'AUX3', + 'Aux4' => 'AUX4', + 'Aux5' => 'AUX5', + 'Aux6' => 'AUX6', + 'Aux7' => 'AUX7', + 'Bluetooth' => 'BT', + 'Usb/iPod' => 'USB/IPOD', + 'Usb_play' => 'USB', + 'iPod_play' => 'IPD', + 'iRadio_play' => 'IRP', + 'Favorites_play' => 'FVP', + 'Source' => 'SOURCE', +# 'Status' => '?', + }, + 'MU' => { + 'on' => 'ON', + 'off' => 'OFF', + 'status' => '?', + }, + 'CS' => { + 'stereo' => 'ST', + 'mono' => 'MONO', + 'status' => '?', + }, + 'CV' => { + 'FL' => 'FrontLeft', + 'FR' => 'FrontRight', + }, + 'HPF' => { + 'on' => 'ON', + 'off' => 'OFF', + 'status' => '?', + }, + 'PS' => { + 'BAS' => 'bass', + 'TRE' => 'treble', + }, + 'HDA' => { + 'HDA' => 'HDMI_out', + 'THR' => 'through', + 'PCM' => 'pcm', + }, + 'SLP' => { + 'off' => 'OFF', + '10min' => '010', + '15min' => '015', + '30min' => '030', + '40min' => '040', + '50min' => '050', + '60min' => '060', + '70min' => '070', + '80min' => '080', + '90min' => '090', + '100min' => '100', + '110min' => '110', + '120min' => '120', + 'status' => '?', + }, + 'STBY' => { + '2h' => '2H', + '4h' => '4H', + '8h' => '8H', + 'off' => 'OFF', + 'status' => '?', + }, + 'SWITCH' => { + "on" => "off", + "off" => "on", + }, + 'REMOTE' => { #Remote - all commands: + 'up' => 'UP', + 'down' => 'DOWN', + }, + 'SOD' => { # used inputs: USE = aviable / DEL = not aviable + 'DVD' => 'USE', + 'BD' => 'USE', + 'TV' => 'USE', + 'SAT/CBL' => 'USE', + 'SAT' => 'DEL', + 'MPLAY' => 'USE', + 'BT' => 'USE', + 'GAME' => 'USE', + 'HDRADIO' => 'DEL', + 'AUX1' => 'USE', + 'AUX2' => 'USE', + 'AUX3' => 'DEL', + 'AUX4' => 'DEL', + 'AUX5' => 'DEL', + 'AUX6' => 'DEL', + 'AUX7' => 'DEL', + 'AUXA' => 'DEL', + 'AUXB' => 'DEL', + 'AUXC' => 'DEL', + 'AUXD' => 'DEL', + 'CD' => 'USE', + 'PHONO' => 'USE', + 'TUNER' => 'USE', + 'FAVORITES' => 'USE', + 'IRADIO' => 'USE', + 'SIRIUSXM' => 'DEL', + 'PANDORA' => 'DEL', + 'SERVER' => 'USE', + 'FLICKR' => 'USE', + 'NET' => 'USE', + 'LASTFM' => 'DEL', + 'USB/IPOD' => 'USE', + 'USB' => 'USE', + 'IPD' => 'USE', + 'IRP' => 'USE', + 'FVP' => 'USE', + 'SOURCE' => 'USE', + }, +}; + +sub +DENON_ZONE_GetValue($;$;$) { + my ( $status, $com, $inf) = @_; + + my $command = (defined($com) ? $com : "na"); + my $info = (defined($inf) ? $inf : "na"); + + if ( $command eq "na" && $info eq "na" + && defined( $DENON_db_zone->{$status} ) ) + { + my $value = eval { $DENON_db_zone->{$status} }; + $value = $@ ? "unknown" : $value; + return $value; + } + elsif ( defined($DENON_db_zone->{$status}{$command} ) && $info eq "na" ) { + my $value = eval { $DENON_db_zone->{$status}{$command} }; + $value = $@ ? "unknown" : $value; + return $value; + } + elsif ( defined($DENON_db_zone->{$status}{$command}{$info}) ) { + my $value = eval { $DENON_db_zone->{$status}{$command}{$info} }; + $value = $@ ? "unknown" : $value; + return $value; + } + else { + return "unknown"; + } +} + +sub +DENON_ZONE_GetKey($$;$) { + my ( $status, $command, $info) = @_; + + if ( defined($status) && defined($command) && !defined($info)) + { + my @keys = keys %{$DENON_db_zone->{$status}}; + my @values = values %{$DENON_db_zone->{$status}}; + while (@keys) { + my $fhemCommand = pop(@keys); + my $denonCommand = pop(@values); + if ($command eq $denonCommand) + { + return $fhemCommand; + } + } + } + if ( defined($status) && defined($command) && defined($info)) + { + my @keys = keys %{$DENON_db_zone->{$status}{$command}}; + my @values = values %{$DENON_db_zone->{$status}{$command}}; + while (@keys) { + my $fhemCommand = pop(@keys); + my $denonCommand = pop(@values); + if ($info eq $denonCommand) + { + return $fhemCommand; + } + } + } + else { + return undef; + } +} + + +################################### +sub +DENON_AVR_ZONE_Initialize($) +{ + my ($hash) = @_; + + $hash->{Match} = ".+"; + + $hash->{GetFn} = "DENON_AVR_ZONE_Get"; + $hash->{SetFn} = "DENON_AVR_ZONE_Set"; + $hash->{DefFn} = "DENON_AVR_ZONE_Define"; + $hash->{UndefFn} = "DENON_AVR_ZONE_Undefine"; + $hash->{ParseFn} = "DENON_AVR_ZONE_Parse"; + + $hash->{AttrFn} = "DENON_AVR_ZONE_Attr"; + $hash->{AttrList} = "IODev brand:Denon,Marantz do_not_notify:1,0 disable:0,1 connectionCheck:off,30,45,60,75,90,105,120,240,300 timeout:1,2,3,4,5 unit:off,on ".$readingFnAttributes; + + $data{RC_makenotify}{DENON_AVR_ZONE} = "DENON_AVR_ZONE_RCmakenotify"; + $data{RC_layout}{DENON_AVR_ZONE_RC} = "DENON_AVR_ZONE_RClayout"; + + $hash->{parseParams} = 1; +} + +############################# +sub +DENON_AVR_ZONE_Define($$$) +{ + my ($hash, $a, $h) = @_; + my $name = $hash->{NAME}; + return "Usage: define DENON_AVR_ZONE ... wrong paramter count: ".int(@$a) if(int(@$a) != 3); + + AssignIoPort($hash); + + my $IOhash = $hash->{IODev}; + my $IOname = $IOhash->{NAME}; + my $zone; + + if(!defined($IOhash) && !defined($IOname)) { + my $err= "DENON_AVR_ZONE $name error: no I/O device."; + Log3 $hash, 1, $err; + return $err; + } + + if ( !defined( @$a[2] ) ) { + $zone = "2"; + } + elsif ( @$a[2] eq "2" || @$a[2] eq "3" || @$a[2] eq "4") { + $zone = @$a[2]; + } + else { + return @$a[2] . " is not a valid Zone number"; + } + + if ( defined($modules{DENON_AVR_ZONE}{defptr}{$IOname}{$zone}) ) + { + return "Zone already defined in " . $modules{DENON_AVR_ZONE}{defptr}{$IOname}{$zone}{NAME}; + } + if ( !defined($IOhash) ) { + return "No matching I/O device found, please define a DENON_AVR device first"; + } + elsif ( !defined( $IOhash->{TYPE} ) || !defined( $IOhash->{NAME} ) ) { + return "IODev does not seem to be existing"; + } + elsif ( $IOhash->{TYPE} ne "DENON_AVR" ) { + return "IODev is not of type DENON_AVR"; + } + else { + $hash->{ZONE} = $zone; + $modules{DENON_AVR_ZONE}{defptr}{$IOname}{$zone} = $hash; + } + + # set default attributes + unless ( exists( $attr{$name}{webCmd} ) ) { + $attr{$name}{webCmd} = 'volume:muteT:input'; + } + unless ( exists( $attr{$name}{cmdIcon} ) ) { + $attr{$name}{cmdIcon} = 'muteT:rc_MUTE'; + } + unless ( exists( $attr{$name}{devStateIcon} ) ) { + $attr{$name}{devStateIcon} = 'on:rc_GREEN:off off:rc_STOP:on absent:rc_RED muted:rc_MUTE@green:muteT'; + } + unless (exists($attr{$name}{stateFormat})){ + $attr{$name}{stateFormat} = 'stateAV'; + } + + return undef; +} + +##################################### +sub DENON_AVR_ZONE_Undefine($$) { + my ( $hash, $name ) = @_; + my $zone = $hash->{ZONE}; + my $IOhash = $hash->{IODev}; + my $IOname = $IOhash->{NAME}; + + Log3 $name, 5, + "DENON_AVR_ZONE $name: called function DENON_AVR_ZONE_Undefine()"; + + delete $modules{DENON_AVR_ZONE}{defptr}{$IOname}{$zone} + if ( defined( $modules{DENON_AVR_ZONE}{defptr}{$IOname}{$zone} ) ); + + DENON_AVR_ZONE_RCdelete($name); + + # Disconnect from device + DevIo_CloseDev($hash); + + # Stop the internal GetStatus-Loop and exit + RemoveInternalTimer($hash); + + return undef; +} + +##################################### +sub +DENON_AVR_ZONE_Parse(@) +{ + my ($IOhash, $msg) = @_; # IOhash points to the DENON_AVR, not to the DENON_AVR_ZONE + + my @matches; + my $name = $IOhash->{NAME}; + + foreach my $d (keys %defs) { + my $hash = $defs{$d}; + my $state = ReadingsVal( $name, "power", "off" ); + + if($hash->{TYPE} eq "DENON_AVR_ZONE" && $hash->{IODev} eq $IOhash) { + + my $return = "unknown"; + my $zone = 2; + my $zonehash = undef; + my $zonename = undef; + + if ( defined($modules{DENON_AVR_ZONE}{defptr}{$name}{2}) && $msg =~ /^Z2/ ) + { + $zone = 2; + $zonehash = $modules{DENON_AVR_ZONE}{defptr}{$name}{2}; + $zonename = $zonehash->{NAME}; + } + elsif ( defined($modules{DENON_AVR_ZONE}{defptr}{$name}{3}) && $msg =~ /^Z3/ ) + { + $zone = 3; + $zonehash = $modules{DENON_AVR_ZONE}{defptr}{$name}{3}; + $zonename = $zonehash->{NAME}; + } + elsif ( defined($modules{DENON_AVR_ZONE}{defptr}{$name}{4}) && $msg =~ /^Z4/ ) + { + $zone = 4; + $zonehash = $modules{DENON_AVR_ZONE}{defptr}{$name}{4}; + $zonename = $zonehash->{NAME}; + } + + my $dezibel = AttrVal($zonename, "unit", "off") eq "on" ? " dB" : ""; + my $percent = AttrVal($zonename, "unit", "off") eq "on" ? " %" : ""; + + Log3 $zonename, 5, "DENON_AVR_ZONE $zone: $name "; + push @matches, $d; + + if ($msg =~ /^(Z2|Z3|Z4)(.+)/) { + my $zone = $1; + my $arg = $2; + + readingsBeginUpdate($zonehash); + + if ($arg eq "ON" || $arg eq "OFF") { + readingsBulkUpdate($zonehash, "power", lc($arg)); + readingsBulkUpdate($zonehash, "state", lc($arg)); + readingsBulkUpdate($zonehash, "presence", "present"); + readingsBulkUpdate($zonehash, "stateAV", DENON_AVR_ZONE_GetStateAV($zonehash)); + $return = lc($arg); + } + elsif ($arg =~ /^CS(.+)/) { + Log3 $zonename, 0, "DENON_AVR_ZONE $zone: $name <$1>"; + my $status = DENON_ZONE_GetKey("CS", $1); + readingsBulkUpdate($zonehash, "channelSetting", $status); + $return = "channelSetting " . $status; + } + elsif ($arg =~ /^CV([A-Z]+) (.+)/) { + Log3 $zonename, 0, "DENON_AVR_ZONE $zone: $name <$1>"; + my $channel = DENON_ZONE_GetValue("CV", $1); + my $volume = $2; + if (length($volume) == 2) + { + $volume = $volume."0"; + } + readingsBulkUpdate($zonehash, "level".$channel, ($volume / 10 - 50).$dezibel); + $return = "level".$channel . " " . ($volume / 10 - 50).$dezibel; + } + elsif ($arg =~ /^FAVORITE(.+)/) + { + readingsBulkUpdate($zonehash, "favorite", $1); + $return = "favorite " . $1; + } + elsif ($arg =~ /^MU(.+)/) { + readingsBulkUpdate($zonehash, "mute", lc($1)); + readingsBulkUpdate($zonehash, "stateAV", DENON_AVR_ZONE_GetStateAV($zonehash)); + $return = "mute " . lc($1); + } + elsif ($arg =~ /^HDA(.+)/) { + my $status = DENON_ZONE_GetValue('HDA', $1); + readingsBulkUpdate($zonehash, "digitalOut", $status); + $return = "digitalOut " . $status; + } + elsif ($arg =~ /^HPF(.+)/) { + readingsBulkUpdate($zonehash, "highPassFilter", lc($1)); + $return = "highPassFilter " . lc($1); + } + elsif ($arg =~/^PS(.+)/) + { + my $parameter = $1; + if($parameter =~ /^([A-Z]{3}) (.+)/) + { + my $status = DENON_ZONE_GetValue('PS', $1); + my $volume = $2; + if (length($volume) == 2) + { + $volume = $volume."0"; + } + $volume = ($volume / 10 - 50).$dezibel; + readingsBulkUpdate($zonehash, $status, $volume); + $return = $status . " " . $volume; + } + } + elsif ($arg =~ /^QUICK(.+)/) { + readingsBulkUpdate($zonehash, "quickselect", $1); + $return = "quickselect " . $1; + } + elsif ($arg =~ /^SMART(.+)/) { + readingsBulkUpdate($zonehash, "smartselect", $1); + $return = "smartselect " . $1; + } + elsif ($arg =~ /^SLP(.+)/) { + readingsBulkUpdate($zonehash, "sleep", $1."min"); + $return = "sleep " . $1."min"; + } + elsif ($arg =~ /^STBY(.+)/) { + my $status = DENON_ZONE_GetKey("STBY", $1); + readingsBulkUpdate($zonehash, "autoStandby", $status); + $return = "autoStandby " . $status; + } + elsif ($arg =~ /(^[0-9]+)/){ + my $volume = $1; + if (length($volume) == 2) + { + $volume = $volume."0"; + } + readingsBulkUpdate($zonehash, "volumeStraight", ($volume / 10 - 80).$percent); + readingsBulkUpdate($zonehash, "volume", ($volume / 10).$dezibel); + $return = "volume/volumeStraight ".($volume / 10)."/".($volume / 10 - 80); + } + else{ + if ($arg eq 'SOURCE'){ + my $status = ReadingsVal($name, "input", "Cbl/Sat"); + readingsBulkUpdate($zonehash, "input", $status); + $return = "input " . $status; + } + else{ + my $status = DENON_ZONE_GetValue($arg); + readingsBulkUpdate($zonehash, "input", $status); + $return = "input " . $status; + } + } + + readingsEndUpdate($zonehash, 1); + } + elsif ( $msg =~ /^power (.+)/ && defined($zonehash) ) { + readingsBeginUpdate($zonehash); + readingsBulkUpdate($zonehash, "power", $1); + readingsEndUpdate($zonehash, 1); + $return = $1; + } + elsif ( $msg =~ /^presence (.+)/ && defined($zonehash) ) { + readingsBeginUpdate($zonehash); + readingsBulkUpdate($zonehash, "presence", $1); + readingsEndUpdate($zonehash, 1); + $return = "presence " . $1; + } + Log3 $zonename, 4, "DENON_AVR_ZONE $zone: parsing <$msg> to <$return>"; + return @matches if (@matches); + return "UNDEFINED DENON_AVR_ZONE"; + } + } +} + +##################################### +sub +DENON_AVR_ZONE_Get($$$) +{ + my ( $hash, $a, $h ) = @_; + my $arg; + my $name = $hash->{NAME}; + my $zone = $hash->{ZONE}; + + return "argument is missing" if (int(@$a) != 2); + if (@$a[1] =~ /^(power|volumeStraight|volume|channelSetting|highPassFilter|mute|input|remotecontrol|autoStandby|sleep|zone|zoneStatusRequest)$/) + { + if (@$a[1] eq "remotecontrol") + { + return DENON_AVR_ZONE_RCmake($name); + } + elsif (@$a[1] eq "zoneStatusRequest") + { + DENON_AVR_ZONE_Command_Write($hash, "?", "power"); + + return "StatusRequest zone $zone finished!"; + } + elsif(defined(ReadingsVal( $name, @$a[1], "" ))) + { + return ReadingsVal( $name, @$a[1], "" ); + } + else + { + return "No such reading: @$a[1]"; + } + } + else + { + return "Unknown argument @$a[1], choose one of power volumeStraight volume autoStandby channelSetting highPassFilter mute input mute remotecontrol sleep zone zoneStatusRequest"; + } +} + +##################################### +sub +DENON_AVR_ZONE_Set($$$) +{ + my ( $hash, $a, $h ) = @_; + + my $IOhash = $hash->{IODev}; + my $name = $hash->{NAME}; + my $zone = $hash->{ZONE}; + my $state = ReadingsVal( $name, "power", "off" ); + my $presence = ReadingsVal( $name, "presence", "absent" ); + my $return; + my $select = "quick"; + + my @channel = (); + my @preset = (01..56); + my @inputs = (); + #my @usedInputs = (); + my @remoteControl = (); + my $dezibel = AttrVal($name, "unit", "off") eq "on" ? " dB" : ""; + + foreach my $key (sort(keys %{$DENON_db_zone->{'REMOTE'}})) { + push(@remoteControl, $key); + } + + if ( exists( $attr{$IOhash->{NAME}}{inputs} ) ) + { + @inputs = split(/,/,$attr{$IOhash->{NAME}}{inputs}); + } + else + { + foreach my $key (sort(keys %{$DENON_db_zone->{'SI'}})) { + my $device = $DENON_db_zone->{'SI'}{$key}; + + if ( defined($DENON_db_zone->{'SOD'}{$device})) + { + if ($DENON_db_zone->{'SOD'}{$device} eq 'USE') + { + push(@inputs, $key); + } + #push(@usedInputs, $key); + } + } + } + + if(AttrVal($name, "brand", "Denon") eq "Marantz") + { + $select = "smart"; + } + + foreach my $key (sort(keys %{$DENON_db_zone->{'CV'}})) { + push(@channel, $DENON_db_zone->{'CV'}{$key}."_up"); + push(@channel, $DENON_db_zone->{'CV'}{$key}."_down"); + } + + my $usage = "Unknown argument @$a[1], choose one of on off toggle volumeDown volumeUp volumeStraight:slider,-80,1,18 volume:slider,0,1,98 mute:on,off,toggle muteT sleep:off,10min,15min,20min,30min,40min,50min,60min,70min,80min,90min,100min,110min,120min autoStandby:off,2h,4h,8h channelSetting:stereo,mono highPassFilter:on,off " . + "favorite:1,2,3,4" . " " . + $select . "select:1,2,3,4,5 " . + "input:" . join(",", @inputs) . " " . +# "usedInputs:multiple-strict," . join(",", @usedInputs) . " " . + "bass:slider,-10,1,10 treble:slider,-10,1,10 " . + "channelVolume:" . join(",", @channel) . " " . + "remoteControl:" . join(",", @remoteControl) . " " . + "rawCommand"; + + if (@$a[1] eq "?") + { + $return = "?"; + return $usage; + } + + readingsBeginUpdate($hash); + + if (@$a[1] =~ /^(on|off)$/) + { + $return = DENON_AVR_ZONE_Command_SetPower($hash, @$a[1]); + } + elsif (@$a[1] eq "bass") + { + my $volume = @$a[2] + 50; + $return = DENON_AVR_ZONE_Command_Write($hash, "PSBAS ".$volume, "bass", @$a[2].$dezibel); + } + elsif (@$a[1] eq "treble") + { + my $volume = @$a[2] + 50; + $return = DENON_AVR_ZONE_Command_Write($hash, "PSTRE ".$volume, "treble", @$a[2].$dezibel); + } + elsif (@$a[1] eq "channelSetting") + { + my $favorite = @$a[2]; + my $channel = DENON_ZONE_GetValue('CS', @$a[2]); + $return = DENON_AVR_ZONE_Command_Write($hash, "CS".$channel, "channelSetting", @$a[2]); + } + elsif (@$a[1] eq "channelVolume") + { + my $channel = ""; + my $command = @$a[2]; + my $volume = ""; + if($command =~ /^(.+)_(up|down)/) + { + $channel = DENON_ZONE_GetKey("CV", $1); + $channel = $channel." ".uc($2); + #my $state = ReadingsVal( $name, $channel, "0" ); + #$volume = uc($2); + $volume = "query_CV?"; + } + else + { + $channel = DENON_ZONE_GetKey("CV", $command); + $volume = @$a[3] + 50; + if ($volume % 1 == 0) + { + $volume = 40 if($volume < 40); + $volume = 60 if($volume > 60); + $volume = sprintf ('%02d', $volume); + $channel = $channel." ".$volume; + $volume = @$a[3].$dezibel; + } + elsif ($volume % 1 == 0.5) + { + $volume = 40.5 if($volume < 40.5); + $volume = 59.5 if($volume > 59.5); + $volume = sprintf ('%03d', ($volume * 10)); + $channel = $channel." ".$volume; + $volume = @$a[3].$dezibel; + } + else + { + return undef; + } + } + $return = DENON_AVR_ZONE_Command_Write($hash, "CV".$channel, "channelVolume", $volume); + } + elsif (@$a[1] eq "favorite") + { + my $favorite = @$a[2]; + $return = DENON_AVR_ZONE_Command_Write($hash, "FAVORITE".$favorite, "favorite", @$a[2]); + } + elsif (@$a[1] eq "highPassFilter") + { + my $favorite = @$a[2]; + my $channel = DENON_ZONE_GetValue('HPF', @$a[2]); + $return = DENON_AVR_ZONE_Command_Write($hash, "HPF".$channel, "highPassFilter", @$a[2]); + } + elsif (@$a[1] eq "input") + { + my $input = DENON_ZONE_GetValue('SI', @$a[2]); + $return = DENON_AVR_ZONE_Command_SetInput($hash, $input, @$a[2]); + } + elsif (@$a[1] eq "mute" || @$a[1] eq "muteT") + { + my $mute = @$a[2]; + if ($mute eq "toggle" || @$a[1] eq "muteT") + { + my $newMuteState = DENON_ZONE_GetValue('SWITCH', ReadingsVal( $name, "mute", "off")); + $return = DENON_AVR_ZONE_Command_SetMute($hash, $newMuteState); + } + else + { + $return = DENON_AVR_ZONE_Command_SetMute($hash, $mute); + } + } + elsif (@$a[1] eq "quickselect") + { + my $msg = "QUICK".@$a[2]; + $return = DENON_AVR_ZONE_Command_Write($hash, $msg, "quickselect", @$a[2]); + } + elsif (@$a[1] eq "smartselect") + { + my $msg = "SMART".@$a[2]; + $return = DENON_AVR_ZONE_Command_Write($hash, $msg, "smartselect", @$a[2]); + } + elsif (@$a[1] eq "sleep") + { + my $msg = DENON_ZONE_GetValue('SLP', @$a[2]); + $return = DENON_AVR_ZONE_Command_Write($hash, "SLP".$msg, "sleep", @$a[2]); + } + elsif (@$a[1] eq "autoStandby") + { + my $msg = DENON_ZONE_GetValue('STBY',@$a[2]); + $return = DENON_AVR_ZONE_Command_Write($hash, "STBY".$msg, "autoStandby", @$a[2]); + } + elsif (@$a[1] eq "toggle") + { + my $newPowerState = DENON_ZONE_GetValue('SWITCH', ReadingsVal( $name, "state", "on")); + $return =DENON_AVR_ZONE_Command_SetPower($hash, $newPowerState); + } + elsif (@$a[1] eq "volumeStraight") + { + my $volume = @$a[2]; + $return = DENON_AVR_ZONE_Command_SetVolume($hash, $volume + 80); + } + elsif (@$a[1] eq "volume") + { + my $volume = @$a[2]; + $return = DENON_AVR_ZONE_Command_SetVolume($hash, $volume); + } + elsif (@$a[1] eq "volumeDown") + { + my $msg = "DOWN"; + my $oldVolume = ReadingsVal( $name, "volume", "0" ); + + my $volume = @$a[2]; + if(@$a[2]) + { + $volume = $oldVolume - $volume; + $return = DENON_AVR_ZONE_Command_SetVolume($hash, $volume); + } + else + { + readingsBulkUpdate($hash, "volumeStraight", $oldVolume - 81); + readingsBulkUpdate($hash, "volume", $oldVolume - 1); + $return = DENON_AVR_ZONE_Command_Write($hash, $msg, "volumeDown"); + } + } + elsif (@$a[1] eq "volumeUp") + { + my $msg = "UP"; + my $oldVolume = ReadingsVal( $name, "volume", "0" ); + + my $volume = @$a[2]; + if(@$a[2]) + { + $volume = $oldVolume + $volume; + $return = DENON_AVR_ZONE_Command_SetVolume($hash, $volume); + } + else + { + readingsBulkUpdate($hash, "volumeStraight", $oldVolume - 79); + readingsBulkUpdate($hash, "volume", $oldVolume + 1); + $return = DENON_AVR_ZONE_Command_Write($hash, $msg, "volumeUp"); + } + } + elsif (@$a[1] eq "rawCommand") + { + my $msg = @$a[2]; + $msg = @$a[2]." ".@$a[3] if defined @$a[3]; + $msg = $msg." ".@$a[4] if defined @$a[4]; + $return = DENON_AVR_ZONE_Command_Write($hash, $msg, "rawCommand"); + } + elsif (@$a[1] eq "remoteControl") + { + if(@$a[2] =~ /^in_(.+)/) #inputs + { + my $input = DENON_ZONE_GetValue('SI', $1); + $return = DENON_AVR_ZONE_Command_SetInput($hash, $input, @$a[2]); + } + if(@$a[2] =~ /^(up|down)$/) #volume + { + $return = DENON_AVR_ZONE_Command_Write($hash, uc($1), "volume"); + } + else + { + fhem("set $name @$a[2]"); + } + } + elsif (@$a[1] eq "usedInputs") + { + DENON_AVR_ZONE_SetUsedInputs($hash, @$a[2]); + $return = ""; + } + else + { + $return = $usage; + } + + readingsEndUpdate( $hash, 1 ); + + # return result + return $return; +} + +##################################### +sub +DENON_AVR_ZONE_Attr($@) +{ + + my @a = @_; + my $hash= $defs{$a[1]}; + + return undef; +} + +##################################### +sub +DENON_AVR_ZONE_Command_Write($$$;$) +{ + my ($hash, $msg, $call, $state) = @_; + my $name = $hash->{NAME}; + my $zone = $hash->{ZONE}; + + Log3 $name, 5, "DENON_AVR_ZONE $name: zone $zone called Write"; + IOWrite($hash, "Z".$zone."".$msg, "Z".$zone." ".$call); + if(defined($state)) + { + if($state =~ /^query_(.+)/) + { + IOWrite($hash, "Z".$zone."".$1, "Z".$zone." query"); + } + else + { + readingsBulkUpdate($hash, $call, $state); + } + } + + return undef; +} + +##################################### +sub +DENON_AVR_ZONE_GetStateAV($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + if ( ReadingsVal( $name, "presence", "absent" ) eq "absent" ) { + return "absent"; + } + elsif ( ReadingsVal( $name, "power", "off" ) eq "off" ) { + return "off"; + } + elsif ( ReadingsVal( $name, "mute", "off" ) eq "on" ) { + return "muted"; + } + else { + return ReadingsVal( $name, "power", "off" ); + } +} + + +##################################### +sub +DENON_AVR_ZONE_Command_SetInput($$$) +{ + my ($hash, $input, $friendlyName) = @_; + my $name = $hash->{NAME}; + my $zone = $hash->{ZONE}; + + Log3 $name, 5, "DENON_AVR_ZONE $name: zone $zone called SetInput."; + IOWrite($hash, "Z".$zone."".$input, "Z".$zone." input"); + readingsBulkUpdate($hash, "input", $friendlyName); + + return undef; +} + +##################################### +sub +DENON_AVR_ZONE_Command_SetMute($$) +{ + my ($hash, $mute) = @_; + my $name = $hash->{NAME}; + my $zone = $hash->{ZONE}; + + Log3 $name, 5, "DENON_AVR_ZONE $name: zone $zone called SetMute."; + + return "mute can only used when device is powered on" if (ReadingsVal( $name, "state", "off") eq "off"); + + my $status = DENON_ZONE_GetValue('MU', lc($mute)); + + IOWrite($hash, "Z".$zone."MU".$status, "Z".$zone." mute"); + readingsBulkUpdate($hash, "mute", $mute); + readingsBulkUpdate($hash, "stateAV", DENON_AVR_ZONE_GetStateAV($hash)); + + return undef; +} + +##################################### +sub +DENON_AVR_ZONE_Command_SetPower($$) +{ + my ($hash, $power) = @_; + my $name = $hash->{NAME}; + my $zone = $hash->{ZONE}; + + Log3 $name, 5, "DENON_AVR_ZONE $name: zone $zone called SetPower"; + + IOWrite($hash, "Z".$zone."".uc($power), "Z".$zone." power"); + + readingsBulkUpdate($hash, "power", lc($power)); + readingsBulkUpdate($hash, "state", lc($power)); + readingsBulkUpdate($hash, "stateAV", DENON_AVR_ZONE_GetStateAV($hash)); + + return undef; +} + +##################################### +sub +DENON_AVR_ZONE_Command_SetVolume($$) +{ + my ($hash, $volume) = @_; + my $name = $hash->{NAME}; + my $zone = $hash->{ZONE}; + + my $dezibel = AttrVal($name, "unit", "off") eq "on" ? " dB" : ""; + my $percent = AttrVal($name, "unit", "off") eq "on" ? " %" : ""; + + Log3 $name, 5, "DENON_AVR_ZONE $name: zone $zone called SetVolume."; + + if(ReadingsVal( $name, "state", "off") eq "off") + { + return "Volume can only set when device is powered on!"; + } + else + { + if (length($volume) == 1) + { + $volume = "0".$volume; + } + + IOWrite($hash, "Z".$zone."".$volume, "Z".$zone." volume"); + readingsBulkUpdate($hash, "volumeStraight", ($volume - 80).$percent); + readingsBulkUpdate($hash, "volume", $volume.$dezibel); + } + + return undef; +} + +##################################### +sub +DENON_AVR_ZONE_SetUsedInputs($$) { + my ($hash, $usedInputs) = @_; + my $name = $hash->{NAME}; + my @inputs = split(/,/,$usedInputs); + my @denonInputs = (); + + foreach (@inputs) + { + if(exists $DENON_db_zone->{'SI'}{$_}) + { + push(@denonInputs, $_); + } + } + $attr{$name}{inputs} = join(",", @denonInputs); +} + +##################################### +sub +DENON_AVR_ZONE_RCmakenotify($$) { + my ( $name, $ndev ) = @_; + my $nname = "notify_$name"; + + fhem( "define $nname notify $name set $ndev remoteControl " . '$EVENT', 1 ); + Log3 $name, 3, "DENON_AVR_ZONE $name: create notify for remoteControl."; + return "Notify created by DENON_AVR_ZONE $nname"; +} + +##################################### +sub +DENON_AVR_ZONE_RCmake($) { + my ( $name ) = @_; + my $device = $name."_RC"; + + if(!defined($defs{$device})) + { + fhem("define $device remotecontrol"); + fhem("sleep 1;set $device layout DENON_AVR_ZONE_RC"); + + my $notify = "notify_$name"; + if(!defined($defs{$notify})) + { + fhem("sleep 1;set $device makenotify $name"); + } + } + + Log3 $name, 3, "DENON_AVR $name: create remoteControl."; + return "Remotecontrol created by DENON_AVR $name"; +} + +##################################### +sub +DENON_AVR_ZONE_RCdelete($) { + my ( $name ) = @_; + my $device = $name."_RC"; + + if(defined($defs{$device})) + { + fhem("delete $device"); + my $notify = "notify_".$device; + + if(defined($defs{$notify})) + { + fhem("sleep 1;delete $notify"); + } + } + + Log3 undef, 3, "DENON_AVR $name: delete remoteControl."; + return "Remotecontrol deleted by DENON_AVR_ZONE: $name"; +} + +##################################### +sub DENON_AVR_ZONE_RClayout() { + my @row; + + $row[0] = "volumeUp:VOLUP,:blank,in_Cbl/Sat:CBLSAT,in_Blu-Ray:BR,in_DVD:DVD,in_CD:CD,:blank,play:PLAY,:blank,toggle:POWEROFF3"; + $row[1] = "muteT:MUTE,:blank,in_Mediaplayer:MEDIAPLAYER,in_iRadio:IRADIO,in_OnlineMusic:ONLINEMUSIC,in_Usb/iPod:IPODUSB,:blank,pause:PAUSE"; + $row[2] = "volumeDown:VOLDOWN,:blank,in_Bluetooth:BT,in_Favorites:FAV,in_Aux1:AUX1,in_Phono:PHONO,:blank,stop:STOP"; + $row[3] = "attr rc_iconpath icons/remotecontrol"; + $row[4] = "attr rc_iconprefix black_btn_"; + + return @row; +} + +1; + + +=pod +=item device +=item summary control for DENON (Marantz) AV receivers zone +=item summary_DE Zonen-Steuerung von DENON (Marantz) AV Receivern +=begin html + +

+ +

+

+ DENON_AVR_ZONE +

+
    + Define +
      + define <name> DENON_AVR_ZONE <zonenumber>
      +
      + This module controls DENON A/V receivers zones.
      +
      +
      + Example:
      +
      +
        + + define avr_zone2 DENON_AVR_ZONE 2
        +
        + define avr_zone2 DENON_AVR_ZONE 3
        +
        +
        +
      +

    +
    + Set +
      + set <name> <command> [<parameter>]
      +
      + Currently, the following commands are defined:
      +
        +
      • + autoStandby   -   set auto standby time +
      • +
      • + favorite   -   switches between favorite (only older models) +
      • +
      • + input   -   switches between inputs +
      • +
      • + mute on,off   -   controls volume mute +
      • +
      • + muteT   -   toggle mute state +
      • +
      • + off   -   turns the device in standby mode +
      • +
      • + on   -   powers on the device +
      • +
      • + quickselect   -   switches between quick select modes (1-5, only new models) +
      • +
      • + rawCommand   -   send raw command to AV receiver +
      • +
      • + remote   -   remote commands (play, stop, pause,...) +
      • +
      • + surroundMode   -   set surround mode +
      • +
      • + toggle   -   switch between on and off +
      • +
      • + volume 0...98   -   set the volume level in percentage +
      • +
      • + volumeStraight -80...18   -   set the volume level in dB +
      • +
      • + volumeUp   -   increases the volume level +
      • +
      • + volumeDown   -   decreases the volume level +
      • +
      +
    +
    + Get +
      + get <name> <what>
      +
      + Currently, the following commands are defined:
      +
        +
      • + remoteControl   -   autocreate remote ccontrol +
      • +
      • + some readings   -   see list below +
      • +
      +

    +
    + Generated Readings/Events:
    +
    +
      +
    • + autoStandby   -   auto standby state +
    • +
    • + input   -   selected input +
    • +
    • + mute   -   mute state +
    • +
    • + power   -   power state +
    • +
    • + state   -   state of AV reciever (on,off,disconnected) +
    • +
    • + stateAV   -   state of AV reciever (on,off,absent,mute) +
    • +
    • + treble   -   treble level in dB +
    • +
    • + videoSelect   -   actual video select mode +
    • +
    • + volume   -   actual volume +
    • +
    • + volumeMax   -   actual maximum volume +
    • +
    • + volumeStraight   -   actual volume straight +
    • +
    +
    + Attributes
    +
    +
      +
    • + IODev   -   Input/Output Device +
    • +
    +
+ +=end html + +=begin html_DE + + +

+ +

+

+ DENON_AVR_ZONE +

+
    + Define +
      + define <name> DENON_AVR_ZONE <zonename[:PORT]>
      +
      + Dieses Modul steuert DENON A/V Receiver über das Netzwerk.
      +
      +
      + Beispiele:
      +
      +
        + + define avr_zone2 DENON_AVR_ZONE 2
        +
        + define avr_zone3 DENON_AVR_ZONE 3
        +
        +
        +
      +

    +
    + Set +
      + set <name> <command> [<parameter>]
      +
      + Momentan sind folgende Befehle verfügbar:
      +
        +
      • + autoStandby   -   Zeit für den Auto-Standby setzen +
      • +
      • + favorite   -   zur Auswahl der Favoriten (nur alte Modelle) +
      • +
      • + input   -   zur Auswahl der Eingänge +
      • +
      • + mute an,aus   -   AV-Receiver laut/stumm schalten +
      • +
      • + muteT   -   zwischen laut und stumm wechseln +
      • +
      • + off   -   Standby AV-Receiver +
      • +
      • + on   -   AV-Receiver anschalten +
      • +
      • + quickselect   -   zur Auswahl der "Quick-Select" Modi (1-5, nur neue Modelle) +
      • +
      • + rawCommand   -   schickt ein "raw command" zum AV-Receiver +
      • +
      • + remote   -   Fernbedienungsbefehle (play, stop, pause,...) +
      • +
      • + surroundMode   -   zur Auswahl der Surround-Modi +
      • +
      • + toggle   -   AV-Receiver an/aus +
      • +
      • + volume 0...98   -   Lautstärke in Prozent +
      • +
      • + volumeStraight -80...18   -   absolute Lautstärke in dB +
      • +
      • + volumeUp   -   erhöht Lautstärke +
      • +
      • + volumeDown   -   erniedrigt Lautstärke +
      • +
      +

    +
    + Get +
      + get <name> <what>
      +
      + Momentan sind folgende Befehle verfügbar:
      +
      +
        +
      • + remoteControl   -   Fernbedienung automatisch erzeugen +
      • +
      • + diverse Readings   -   siehe Liste unten +
      • +
      +

    +
    + Erzeugte Readings/Events:
    +
    +
      +
    • + autoStandby   -   Standbyzustand des AV-Recievers +
    • +
    • + bass   -   Bass-Level in dB +
    • +
    • + display   -   Dim-Status des Displays +
    • +
    • + input   -   gewählte Eingangsquelle +
    • +
    • + levelFrontLeft   -   Pegel des linken Frontlautsprechers in dB +
    • +
    • + levelFrontRight   -   Pegel des rechten Frontlautsprechers in dB +
    • +
    • + mute   -   Status der Stummschaltung +
    • +
    • + power   -   Einschaltzustand des AV-Recievers +
    • +
    • + sound   -   aktueller Sound-Modus +
    • +
    • + state   -   Status des AV-Recievers (on,off,disconnected) +
    • +
    • + stateAV   -   stateAV-Status des AV-Recievers (on,off,mute,absent) +
    • +
    • + toneControl   -   Status der Klangkontrolle +
    • +
    • + treble   -   Höhen-Level in dB +
    • +
    • + videoSelect   -   gewählter Videoselect-Modus +
    • +
    • + volume   -   aktuelle Lautstärke in Prozent +
    • +
    • + volumeMax   -   maximale Lautstärke in Prozent +
    • +
    • + volumeStraight   -   aktuelle absolute Lautstärke in dB +
    • +
    +
    + Attribute
    +
    +
      +
    • + IODev   -   Input/Output Device +
    • +
    +
+ + +=end html_DE + +=cut + +