From e5eb7a5d02a00c17c9d445acc1529ea032e9ed05 Mon Sep 17 00:00:00 2001 From: rapster Date: Mon, 17 Aug 2015 10:56:30 +0000 Subject: [PATCH] 70_VolumeLink: New Module 70_VolumeLink git-svn-id: https://svn.fhem.de/fhem/trunk@9083 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/70_VolumeLink.pm | 363 ++++++++++++++++++++++++++++++++ fhem/MAINTAINER.txt | 1 + fhem/docs/commandref_frame.html | 1 + 3 files changed, 365 insertions(+) create mode 100644 fhem/FHEM/70_VolumeLink.pm diff --git a/fhem/FHEM/70_VolumeLink.pm b/fhem/FHEM/70_VolumeLink.pm new file mode 100644 index 000000000..425c39b0d --- /dev/null +++ b/fhem/FHEM/70_VolumeLink.pm @@ -0,0 +1,363 @@ +############################################################################### +# $Id: 70_VolumeLink.pm 2015-08-17 08:00 - rapster - rapster at x0e.de $ + +package main; +use strict; +use warnings; +use POSIX; +use HttpUtils; +use Time::HiRes qw(gettimeofday time); +use Scalar::Util; +############################################################################### + +sub VolumeLink_Initialize($$) { + my ($hash) = @_; + $hash->{DefFn} = "VolumeLink_Define"; + $hash->{UndefFn} = "VolumeLink_Undef"; + $hash->{SetFn} = "VolumeLink_Set"; + $hash->{AttrFn} = 'VolumeLink_Attr'; + $hash->{AttrList} = "disable:1,0 " + ."ampInputReading " + ."ampInputReadingVal " + ."ampVolumeReading " + ."ampVolumeCommand " + ."ampMuteReading " + ."ampMuteReadingOnVal " + ."ampMuteReadingOffVal " + ."ampMuteCommand " + ."volumeRegexPattern " + .$readingFnAttributes; +} +############################################################################### + +sub VolumeLink_Define($$) { + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + return "Wrong syntax: use define VolumeLink [ [ []]]" if(int(@a) < 5); + + my $name = $a[0]; + + %$hash = ( %$hash, + STARTED => $hash->{STARTED} || 0, + interval => $a[2], + url => $a[3], + ampDevice => $a[4], + timeout => $a[5] || 1, + httpErrorLoglevel => $a[6] || 4, + httpLoglevel => $a[7] || 5, + volumeRegexPattern => $attr{$name}{volumeRegexPattern} || qr/current":(\d+).*muted":(\w+|\d+)/, + ampInputReading => $attr{$name}{ampInputReading} || 'currentTitle', + ampInputReadingVal => $attr{$name}{ampInputReadingVal} || qr/SPDIF-Wiedergabe|^$/, + ampVolumeReading => $attr{$name}{ampVolumeReading} || 'Volume', + ampVolumeCommand => $attr{$name}{ampVolumeCommand} || 'Volume', + ampMuteReading => $attr{$name}{ampMuteReading} || 'Mute', + ampMuteReadingOnVal => $attr{$name}{ampMuteReadingOnVal} || 1, + ampMuteReadingOffVal => $attr{$name}{ampMuteReadingOffVal} || 0, + ampMuteCommand => $attr{$name}{ampMuteCommand} || 'Mute' + ); + $hash->{httpParams} = { + hash => $hash, + url => $hash->{url}, + timeout => $hash->{timeout}, + noshutdown => 1, + loglevel => $hash->{httpLoglevel}, + errorLoglevel => $hash->{httpErrorLoglevel}, + method => 'GET', + callback => \&VolumeLink_ReceiveCommand + }; + + readingsSingleUpdate($hash,'state','off',1) if($hash->{STARTED} == 0 && ReadingsVal($name,'state','') ne 'off'); + + Log3 $name, 3, "$name: Defined with interval:$hash->{interval}, url:$hash->{url}, timeout:$hash->{timeout}, ampDevice:$hash->{ampDevice}"; + + return undef; +} +############################################################################### + +sub VolumeLink_Undef($$) { + my ($hash,$arg) = @_; + + $hash->{STARTED} = 0; + RemoveInternalTimer ($hash); + + Log3 $hash->{NAME}, 3, "$hash->{NAME}: STOPPED"; + + return undef; +} +############################################################################### + +sub VolumeLink_Set($@) { + my ($hash,@a) = @_; + return "\"set $hash->{NAME}\" needs at least an argument" if ( @a < 2 ); + + my ($name,$setName,$setVal) = @a; + + if (AttrVal($name, "disable", 0)) { + Log3 $name, 5, "$name: set called with $setName but device is disabled" if ($setName ne "?"); + return undef; + } + Log3 $name, 5, "$name: set called with $setName " . ($setVal ? $setVal : "") if ($setName ne "?"); + + if($setName !~ /on|off/) { + return "Unknown argument $setName, choose one of on:noArg off:noArg"; + } else { + Log3 $name, 4, "VolumeLink: set $name $setName"; + + if ($setName eq 'on') { + if($hash->{STARTED} == 0) { + + $hash->{STARTED} = 1; + + Log3 $name, 3, "$name: STARTED"; + readingsSingleUpdate($hash,"state",$setName,1); + + VolumeLink_SendCommand($hash); + } + } + elsif ($setName eq 'off') { + if($hash->{STARTED} == 1) { + + $hash->{STARTED} = 0; + RemoveInternalTimer($hash); + + Log3 $name, 3, "$name: STOPPED"; + readingsSingleUpdate($hash,"state",$setName,1); + } + } + } + return undef; +} +############################################################################### + +sub VolumeLink_Attr(@) { + my ($cmd,$name,$attr_name,$attr_value) = @_; + + if($cmd eq "set") { + if($attr_name eq "disable" && $attr_value == 1) { + CommandSet(undef, $name.' off'); + } + $defs{$name}{ampInputReading} = $attr_value if($attr_name eq 'ampInputReading'); + $defs{$name}{ampInputReadingVal} = qr/$attr_value/ if($attr_name eq 'ampInputReadingVal'); + $defs{$name}{ampVolumeReading} = $attr_value if($attr_name eq 'ampVolumeReading'); + $defs{$name}{ampVolumeCommand} = $attr_value if($attr_name eq 'ampVolumeCommand'); + $defs{$name}{ampMuteReading} = $attr_value if($attr_name eq 'ampMuteReading'); + $defs{$name}{ampMuteReadingOnVal} = $attr_value if($attr_name eq 'ampMuteReadingOnVal'); + $defs{$name}{ampMuteReadingOffVal} = $attr_value if($attr_name eq 'ampMuteReadingOffVal'); + $defs{$name}{ampMuteCommand} = $attr_value if($attr_name eq 'ampMuteCommand'); + $defs{$name}{volumeRegexPattern} = qr/$attr_value/ if($attr_name eq 'volumeRegexPattern'); + } + elsif($cmd eq "del") { + $defs{$name}{ampInputReading} = 'currentTitle' if($attr_name eq 'ampInputReading'); + $defs{$name}{ampInputReadingVal} = qr/SPDIF-Wiedergabe|^$/ if($attr_name eq 'ampInputReadingVal'); + $defs{$name}{ampVolumeReading} = 'Volume' if($attr_name eq 'ampVolumeReading'); + $defs{$name}{ampVolumeCommand} = 'Volume' if($attr_name eq 'ampVolumeCommand'); + $defs{$name}{ampMuteReading} = 'Mute' if($attr_name eq 'ampMuteReading'); + $defs{$name}{ampMuteReadingOnVal} = '1' if($attr_name eq 'ampMuteReadingOnVal'); + $defs{$name}{ampMuteReadingOffVal} = '0' if($attr_name eq 'ampMuteReadingOffVal'); + $defs{$name}{ampMuteCommand} = 'Mute' if($attr_name eq 'ampMuteCommand'); + $defs{$name}{volumeRegexPattern} = qr/current":(\d+).*muted":(\w+|\d+)/ if($attr_name eq 'volumeRegexPattern'); + } + return undef; +} +############################################################################### + +sub VolumeLink_SendCommand($) { + my ($hash) = @_; + + Log3 $hash->{NAME}, 5, "$hash->{NAME}: SendCommand - executed"; + + HttpUtils_NonblockingGet($hash->{httpParams}); + + return undef; +} +############################################################################### + +sub VolumeLink_ReceiveCommand($) { + my ($param, $err, $data) = @_; + my $name = $param->{hash}->{NAME}; + + Log3 $name, 5, "$name: ReceiveCommand - executed"; + + if($err ne "") { + Log3 $name, $param->{errorLoglevel}, "$name: Error while requesting ".$param->{url}." - $err"; + + readingsSingleUpdate($param->{hash},'lastHttpError',$err,0); + } + elsif($data ne "") { + Log3 $name, $param->{loglevel}, "$name: url ".$param->{url}." returned: $data"; + + my($vol,$mute) = $data =~ /$param->{hash}->{volumeRegexPattern}/m; + $vol = int($vol); + + if(looks_like_number($vol) && $mute =~ /true|false|0|1/i) { + Log3 $name, 5, "$name: currentVolume: '$vol' - muted: '$mute' - Set it now..."; + readingsBeginUpdate($param->{hash}); + readingsBulkUpdate($param->{hash}, 'volume', $vol ); + readingsBulkUpdate($param->{hash}, 'mute', $mute ); + readingsEndUpdate($param->{hash}, 0); + + my $ampMute = ReadingsVal($param->{hash}->{ampDevice},$param->{hash}->{ampMuteReading},'N/A'); + my $ampVol = ReadingsVal($param->{hash}->{ampDevice},$param->{hash}->{ampVolumeReading},'N/A'); + my $ampTitle = ReadingsVal($param->{hash}->{ampDevice},$param->{hash}->{ampInputReading},'N/A'); + + if($ampMute eq 'N/A' || $ampVol eq 'N/A' || $ampTitle eq 'N/A') { + Log3 $name, 1, "$name: FAILURE, can not fetch an amp-reading! End now... - ampMute:'$ampMute' - ampVol:'$ampVol' - ampInput:'$ampTitle' "; + CommandSet(undef, $name.' off'); + return; + } + + if($ampTitle =~ /$param->{hash}->{ampInputReadingVal}/i) { + if($vol ne $ampVol) { + CommandSet(undef, $param->{hash}->{ampDevice}.' '.$param->{hash}->{ampVolumeCommand}.' '.$vol); + } + if($mute =~ /true|1/i && $ampMute eq $param->{hash}->{ampMuteReadingOffVal}) { + CommandSet(undef, $param->{hash}->{ampDevice}.' '.$param->{hash}->{ampMuteCommand}.' '.$param->{hash}->{ampMuteReadingOnVal}); + } + if($mute =~ /false|0/i && $ampMute eq $param->{hash}->{ampMuteReadingOnVal}) { + CommandSet(undef, $param->{hash}->{ampDevice}.' '.$param->{hash}->{ampMuteCommand}.' '.$param->{hash}->{ampMuteReadingOffVal}); + } + }else { + Log3 $name, 5, "$name: current amp-input: '$ampTitle' not match configured input.' - Skip setting volume in this turn..."; + } + } + else { + Log3 $name, 1, "$name: FAILURE, volumeRegexPattern delivers bad volume or mute state! End now... - volume/\$1:'$vol' - mute/\$2:'$mute'"; + + CommandSet(undef, $name.' off'); + + return; + } + } + + if($param->{hash}->{STARTED} == 1) { + InternalTimer(time()+$param->{hash}->{interval}, 'VolumeLink_SendCommand', $param->{hash}, 0); + } + return undef; +} +############################################################################### + + +1; + +=pod +=begin html + + +

VolumeLink

+
    + +VolumeLink links the volume & mute from a physical device (e.g. a Philips-TV) with the volume & mute control of a fhem device (e.g. a SONOS-Playbar, Onkyo, Yamaha or Denon Receiver, etc.). +

    + +

    Define

    +
      + define <name> VolumeLink <interval> <url> <ampDevice> [<timeout> [<httpErrorLoglevel> [<httpLoglevel>]]] +

      +
      + <interval>: +
        + interval to fetch current volume & mute level from physical-device.
        +
      + <url>: +
        + url to fetch volume & mute level, see Example below. (Example applies to many Philips TV's)
        +
      + <ampDevice>: +
        + the amplifier fhem-device.
        +
      + [<timeout>]: +
        + optional: timeout of a http-get. default: 1 second
        +
      + [<httpErrorLoglevel>]: +
        + optional: loglevel of http-errors. default: 4
        +
      + [<httpLoglevel>]: +
        + optional: loglevel of http-messages. default: 5
        +
      +
    +
    + +

    Example

    +
      + Note: This example will work out of the box with many Philips TV's and a SONOS-Playbar as fhem-device.

      + define tvVolume_LivingRoom VolumeLink 0.2 http://192.168.1.156:1925/5/audio/volume Sonos_LivingRoom
      + set tvVolume_LivingRoom on
      +
    +
    + +

    Set

    +
      + set <name> <on|off>
      +
      + Set on or off, to start or to stop. +
    +
    + +

    Get

      N/A

    + +

    Attributes

    +
      + Note:
      + - All Attributes takes effect immediately.
      + - The default value of volumeRegexPattern applies to many Philips-TV's, otherwise it must be configured.
      + - The default values of amp* applies to a SONOS-Playbar, otherwise it must be configured.
      +
      +
    • disable <1|0>
      + With this attribute you can disable the whole module.
      + If set to 1 the module will be stopped and no volume will be fetched from physical-device or transfer to the amplifier-device.
      + If set to 0 you can start the module again with: set <name> on.
    • +
    • ampInputReading <value>
      + Name of the Input-Reading on amplifier-device
      + Default (which applies to SONOS-Player's): currentTitle
    • +
    • ampInputReadingVal <RegEx>
      + RegEx for the Reading value of the corresponding Input-Channel on amplifier-device
      + Default (which applies to a SONOS-Playbar's SPDIF-Input and if no Input is selected): SPDIF-Wiedergabe|^$
    • +
    • ampVolumeReading <value>
      + Name of the Volume-Reading on amplifier-device
      + Default: Volume
    • +
    • ampVolumeCommand <value>
      + Command to set the volume on amplifier device
      + Default: Volume
    • +
    • ampMuteReading <value>
      + Name of the Mute-Reading on amplifier-device
      + Default: Mute
    • +
    • ampMuteReadingOnVal <value>
      + Reading value if muted
      + Default: 1
    • +
    • ampMuteReadingOffVal <value>
      + Reading value if not muted
      + Default: 0
    • +
    • ampMuteCommand <value>
      + Command to mute the amplifier device
      + Default: Mute
    • +
    • volumeRegexPattern <RegEx>
      + RegEx which is applied to url return data.
      + Must return a number in $1 for volume-level and true, false, 1 or 0 as mute-state in $2.
      + Default (which applies to many Phlips-TV's): current":(\d+).*muted":(\w+|\d+)
    • +

    + +

    Readings

    +
      + Note: All VolumeLink Readings except of 'state' does not generate events!
      +
      +
    • lastHttpError
      + The last HTTP-Error will be recorded in this reading.
      + Define httpErrorLoglevel, httpLoglevel or attribute verbose for more information.
      + Note: Attr verbose will not output all HTTP-Messages, define httpLoglevel for this.
    • +
    • mute
      + The current mute-state fetched from physical device.
    • +
    • volume
      + The current volume-level fetched from physical device.
    • +
    • state
      + on if VolumeLink is running, off if VolumeLink is stopped.
    • +
    +
    + +
+ +=end html +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index fe78508e2..f58556f8f 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -206,6 +206,7 @@ FHEM/70_STV.pm bentele http://forum.fhem.de Sonstiges FHEM/70_TellStick.pm real-wusel http://forum.fhem.de Sonstiges FHEM/70_USBWX.pm wherzig http://forum.fhem.de Sonstiges FHEM/70_VIERA.pm teevau http://forum.fhem.de Sonstiges +FHEM/70_VolumeLink.pm rapster http://forum.fhem.de Multimedia FHEM/70_WS3600.pm Josch http://forum.fhem.de Sonstiges FHEM/70_XBMC.pm vbs http://forum.fhem.de Multimedia FHEM/70_Pushbullet.pm fhainz http://forum.fhem.de Unterstuetzende Dienste diff --git a/fhem/docs/commandref_frame.html b/fhem/docs/commandref_frame.html index 046a70840..1bdb229ec 100644 --- a/fhem/docs/commandref_frame.html +++ b/fhem/docs/commandref_frame.html @@ -142,6 +142,7 @@ Twilight   THRESHOLD   Utils   + VolumeLink   WeekdayTimer   watchdog   weblink