diff --git a/FHEM/17_SIS_PMS.pm b/FHEM/17_SIS_PMS.pm new file mode 100755 index 000000000..eb39b1fa1 --- /dev/null +++ b/FHEM/17_SIS_PMS.pm @@ -0,0 +1,198 @@ +################################################################ +# +# Copyright notice +# +# (c) 2009 Copyright: Kai 'wusel' Siering (wusel+fhem at uu dot org) +# All rights reserved +# +# This code 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. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script 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. +# +# This copyright notice MUST APPEAR in all copies of the script! +############################################### + +########################### +# 17_SIS_PMS.pm +# Module for FHEM +# +# Contributed by Kai 'wusel' Siering in 2010 +# Based in part on work for FHEM by other authors ... +# $Id: 17_SIS_PMS.pm,v 1.1 2010-01-16 22:08:32 painseeker Exp $ +########################### + +package main; + +use strict; +use warnings; + + +sub +SIS_PMS_Initialize($) +{ + my ($hash) = @_; + + $hash->{Match} = "^socket ..:..:..:..:.. . state o.*"; + $hash->{SetFn} = "SIS_PMS_Set"; +# $hash->{StateFn} = "SIS_PMS_SetState"; + $hash->{DefFn} = "SIS_PMS_Define"; + $hash->{UndefFn} = "SIS_PMS_Undef"; + $hash->{ParseFn} = "SIS_PMS_Parse"; +} + + +############################# +sub +SIS_PMS_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + my $u = "wrong syntax: define SIS_PMS "; + + return $u if(int(@a) < 4); + + my $serial = $a[2]; + my $socketnr = $a[3]; + my $name = $a[0]; + my $serialnocolon=$serial; + $serialnocolon =~ s/:/_/g; + + $modules{SIS_PMS}{defptr}{$name} = $hash; + $hash->{SERIAL} = $serial; + $hash->{SOCKET} = $socketnr; + $hash->{NAME} = $name; + $modules{SIS_PMS}{defptr}{$serialnocolon . $socketnr} = $hash; + $hash->{PREV}{STATE} = "undefined"; + AssignIoPort($hash); +} + + +############################# +sub +SIS_PMS_Undef($$) +{ + my ($hash, $name) = @_; +# +# foreach my $c (keys %{ $hash->{CODE} } ) { +# $c = $hash->{CODE}{$c}; +# +# # As after a rename the $name my be different from the $defptr{$c}{$n} +# # we look for the hash. +# foreach my $dname (keys %{ $modules{SIS_PMS}{defptr}{$c} }) { +# delete($modules{SIS_PMS}{defptr}{$c}{$dname}) +# if($modules{SIS_PMS}{defptr}{$c}{$dname} == $hash); +# } +# } + return undef; +} + + +############################# +sub +SIS_PMS_Parse($$) +{ + my ($hash, $msg) = @_; + my $serial; + my $socknr; + my $sockst; + my $dummy; + my $serialnocolon; + + + # Msg format: + # ^socket ..:..:..:..:.. . state o.*"; + + ($dummy, $serial, $socknr, $dummy, $sockst) = split(' ', $msg); + $serialnocolon=$serial; + $serialnocolon =~ s/:/_/g; + + my $def = $modules{SIS_PMS}{defptr}{$serialnocolon . $socknr}; + if($def) { + Log 5, "SIS_PMS: Found device as " . $def->{NAME}; + if($def->{STATE} ne $sockst) { + $def->{READINGS}{PREVSTATE}{TIME} = TimeNow(); + $def->{READINGS}{PREVSTATE}{VAL} = $def->{STATE}; + Log 3, "SIS_PMS " . $def->{NAME} ." state changed from " . $def->{STATE} . " to $sockst"; + $def->{PREV}{STATE} = $def->{STATE}; + $def->{CHANGED}[0] = $sockst; + DoTrigger($def->{NAME}, undef); + } + $def->{STATE} = $sockst; + $def->{READINGS}{STATE}{TIME} = TimeNow(); + $def->{READINGS}{STATE}{VAL} = $sockst; + Log 5, "SIS_PMS " . $def->{NAME} ." state $sockst"; + + return $def->{NAME}; + } else { + my $devname=$serial; + + $devname =~ s/:/_/g; + Log 3, "SIS_PMS Unknown device $serial $socknr, please define it"; + return "UNDEFINED SIS_PMS_$devname.$socknr SIS_PMS $serial $socknr"; + } +} + + +################################### +sub +SIS_PMS_Set($@) +{ + my ($hash, @a) = @_; + my $ret = undef; + my $na = int(@a); + + my $what = lc($a[1]); + + return "no set value specified" if($na < 2 || $na > 3); + +# Log 3, "SIS_PM_Set entered for " . $hash->{NAME}; + + if ($what ne "on" && $what ne "off" && $what ne "toggle") { + return "Unknown argument $what, choose one of on off toggle"; + } + + my $prevstate=$hash->{STATE}; + my $currstate=$what; + + if($what eq "toggle") { + if($prevstate eq "on") { + $currstate="off"; + } elsif($prevstate eq "off") { + $currstate="on"; + } + } + + if($prevstate ne $currstate) { + $hash->{READINGS}{PREVSTATE}{TIME} = TimeNow(); + $hash->{READINGS}{PREVSTATE}{VAL} = $prevstate; + Log 3, "SIS_PMS " . $hash->{NAME} ." state changed from $prevstate to $currstate"; + $hash->{PREV}{STATE} = $prevstate; + $hash->{CHANGED}[0] = $currstate; + $hash->{STATE} = $currstate; + $hash->{READINGS}{STATE}{TIME} = TimeNow(); + $hash->{READINGS}{STATE}{VAL} = $currstate; +# DoTrigger($hash->{NAME}, undef); + } + + my $msg; + $msg=sprintf("%s %s %s", $hash->{SERIAL}, $hash->{SOCKET}, $what); + + IOWrite($hash, $what, $msg); + + return $ret; +} + + +1; diff --git a/FHEM/70_SISPM.pm b/FHEM/70_SISPM.pm new file mode 100644 index 000000000..bc531cc28 --- /dev/null +++ b/FHEM/70_SISPM.pm @@ -0,0 +1,358 @@ +################################################################ +# +# Copyright notice +# +# (c) 2009 Copyright: Kai 'wusel' Siering (wusel+fhem at uu dot org) +# All rights reserved +# +# This code 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. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script 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. +# +# This copyright notice MUST APPEAR in all copies of the script! +############################################### + +########################### +# 70_SISPM.pm +# Module for FHEM +# +# Contributed by Kai 'wusel' Siering in 2010 +# Based in part on work for FHEM by other authors ... +# $Id: 70_SISPM.pm,v 1.1 2010-01-16 22:08:32 painseeker Exp $ +########################### + +package main; + +use strict; +use warnings; + +my %sets = ( + "cmd" => "", + "freq" => "", +); + +my %TranslatedCodes = ( + "Date" => "Date", +); + +my %WantedCodesForStatus = ( + "Ti" => "Ti:", +); + +##################################### +sub +SISPM_Initialize($) +{ + my ($hash) = @_; + +# Consumer + $hash->{DefFn} = "SISPM_Define"; + $hash->{Clients} = + ":SIS_PMS:"; + my %mc = ( + "1:SIS_PMS" => "^socket ..:..:..:..:.. . state o.*", + ); + $hash->{MatchList} = \%mc; + $hash->{AttrList}= "model:SISPM loglevel:0,1,2,3,4,5,6"; + $hash->{ReadFn} = "SISPM_Read"; + $hash->{WriteFn} = "SISPM_Write"; + $hash->{UndefFn} = "SISPM_Undef"; +} + +##################################### +sub +SISPM_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + my $numdetected=0; + my $currentdevice=0; + + return "Define the /path/to/sispmctl as a parameter" if(@a != 3); + + my $FH; + my $dev = sprintf("%s", $a[2]); + Log 3, "SISPM using \"$dev\" as parameter to open(); trying ..."; + my $tmpdev=sprintf("%s -s 2>&1 |", $dev); + open($FH, $tmpdev); + if(!$FH) { + return "SISPM Can't start $dev: $!"; + } + local $_; + while (<$FH>) { + if(/^(No GEMBIRD SiS-PM found.)/) { + Log 3, "SISPM woops? $1"; + } + + if(/^Gembird #(\d+) is USB device (\d+)./) { + Log 3, "SISPM found SISPM device number $1 as USB $2"; + $hash->{UNITS}{$1}{USB}=$2; + $currentdevice=$numdetected; + $numdetected++; + } + if(/^This device has a serial number of (.*)/) { + Log 3, "SISPM device number " . $currentdevice . " has serial $1"; + $hash->{UNITS}{$currentdevice}{SERIAL}=$1; + $hash->{SERIALS}{$1}{UNIT}=$currentdevice; + $hash->{SERIALS}{$1}{USB}=$hash->{UNITS}{$currentdevice}{USB}; + } + } + close($FH); + Log 3, "SISPM initial read done"; + + if ($numdetected==0) { + return "SISPM no SIMPM devices found."; + } + + $hash->{NumPMs} = $numdetected; + $hash->{DeviceName} = $dev; + $hash->{Timer} = 30; # just a keepalive for now + + Log 3, "SISPM setting callback timer"; + + my $oid = $init_done; + $init_done = 1; + InternalTimer(gettimeofday()+ $hash->{Timer}, "SISPM_GetStatus", $hash, 1); + $init_done = $oid; + + Log 3, "SISPM initialized"; + + $hash->{STATE} = "initialized"; + return undef; +} + +##################################### +sub +SISPM_Undef($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + my $name = $hash->{NAME}; + + if(defined($hash->{FD})) { + close($hash->{FD}); + delete $hash->{FD}; + } + delete $selectlist{"$name.pipe"}; + + $hash->{STATE}='undefined'; + Log 3, "$name shutdown complete"; + return undef; +} + + +##################################### +sub +SISPM_GetStatus($) +{ + my ($hash) = @_; + my $dnr = $hash->{DEVNR}; + my $name = $hash->{NAME}; + my $dev = $hash->{DeviceName}; + my $FH; + + # Call us in n seconds again. +# InternalTimer(gettimeofday()+ $hash->{Timer}, "SISPM_GetStatus", $hash, 1); + + Log 4, "SISPM contacting device"; + + my $tmpdev=sprintf("%s -s -g all 2>&1 |", $dev); + open($FH, $tmpdev); + if(!$FH) { + return "SISPM Can't open pipe: $dev: $!"; + } + + $hash->{FD}=$FH; + $selectlist{"$name.pipe"} = $hash; + Log 4, "SISPM pipe opened"; + $hash->{STATE} = "querying"; + $hash->{pipeopentime} = time(); +# InternalTimer(gettimeofday() + 6, "SISPM_Read", $hash, 1); +# return $hash->{STATE}; +} + +##################################### +sub +SISPM_Read($) +{ + my ($hash) = @_; + my $dnr = $hash->{DEVNR}; + my $name = $hash->{NAME}; + my $dev = $hash->{DeviceName}; + my $FH; + my $inputline; + + Log 4, "SISPM Read entered"; + + if(!defined($hash->{FD})) { + Log 3, "Oops, SISPM FD undef'd"; + return undef; + } + if(!$hash->{FD}) { + Log 3, "Oops, SISPM FD empty"; + return undef; + } + $FH = $hash->{FD}; + + Log 4, "SISPM reading started"; + + my @lines; + my $eof; + my $i=0; + my $tn = TimeNow(); + my $reading; + my $readingforstatus; + my $currentserial="none"; + + ($eof, @lines) = nonblockGetLinesSISPM($FH); + + if(!defined($eof)) { + Log 4, "SISPM FIXME: eof undefined?!"; + $eof=0; + } + Log 4, "SISPM reading ended with eof==$eof"; + + # FIXME! Current observed behaviour is "would block", then read of only EOF. + # Not sure if it's always that way; more correct would be checking + # for empty $inputline or undef'd $rawreading,$val. -wusel, 2010-01-04 + if($eof != 1) { + foreach my $inputline ( @lines ) { + $inputline =~ s/\s+$//; + + # wusel, 2010-01-16: Seems as if reading not always works as expected; + # throw away the whole readings if there's a NULL + # serial number. + if($currentserial eq "00:00:00:00:00") { + next; + } + + if($inputline =~ /^(No GEMBIRD SiS-PM found.)/) { + Log 3, "SISPM woops? $1"; + } + + if($inputline =~ /^Gembird #(\d+) is USB device (\d+)\./ || + $inputline =~ /^Accessing Gembird #(\d+) USB device (\d+)/) { + Log 5, "SISPM found SISPM device number $1 as USB $2"; + if($hash->{UNITS}{$1}{USB}!=$2) { +# -wusel, 2010-01-15: FIXME! Verify that this IS the device we're +# looking for. As USB renumbering can happen, +# if the $hash->{UNITS}{$1}{USB}=$2 not matches +# we'd need to redo a "sispmctl -s", maybe by +# going to SISPM_Define() again? +# +# Hoping the best for now -- DON'T TOUCH RUNNING +# BOX WITH MORE THAN ONE SISPM CONNECTED OR HELL +# MAY BREAK LOOSE ;) + Log 3, "SISPM: Odd, got unit $1 as USB $2, have $1 on file as " . $hash->{UNITS}{$1}{USB} . "?"; + } + } + +# -wusel, 2010-01-15: FIXME! This will break on >1 PMS! + if($inputline =~ /^This device has a serial number of (.*)/) { + $currentserial=$1; + if($currentserial eq "00:00:00:00:00") { + Log 3, "SISPM Whooopsie! Something funny has happend, your serial nullified ($currentserial). That's an error and we bail out here."; + next; + } + } + + if($inputline =~ /^Status of outlet (\d):\s+(.*)/) { + if($currentserial ne "none") { + Log 5, "SISPM found socket $1 on $currentserial, state $2"; + my $dmsg="socket " . $currentserial . " $1 state " . $2; + my %addvals; + Dispatch($hash, $dmsg, \%addvals); + } else { + Log 3, "SISPM Whooopsie! Found socket $1, state $2, but no serial (serial is $currentserial)?"; + } + } + } + } + + if($eof) { + close($FH); + delete $hash->{FD}; + delete $selectlist{"$name.pipe"}; + InternalTimer(gettimeofday()+ $hash->{Timer}, "SISPM_GetStatus", $hash, 1); + Log 4, "SISPM done reading pipe"; + } else { + Log 4, "SISPM (further) reading would block"; + } +} + + +##################################### +sub SISPM_Write($$$) { + my ($hash,$fn,$msg) = @_; + my $dev = $hash->{DeviceName}; + +# Log 3, "SISPM_Write entered for $hash->{NAME} with $fn and $msg"; + + my ($serial, $socket, $what) = split(' ', $msg); + + my $deviceno; + my $cmdline; + my $cmdletter="t"; + + if($what eq "on") { + $cmdletter="o"; + } elsif($what eq "off") { + $cmdletter="f"; + } + + if(defined($hash->{SERIALS}{$serial}{UNIT})) { + $deviceno=($hash->{SERIALS}{$serial}{UNIT})+1; + $cmdline=sprintf("%s -d %d -%s %d 2>&1 >/dev/null", $dev, $deviceno, $cmdletter, $socket); + system($cmdline); + } else { + Log 2, "SISPM_Write can not find SISPM device with serial $serial"; + } + return; +} + + +# From http://www.perlmonks.org/?node_id=713384 / http://davesource.com/Solutions/20080924.Perl-Non-blocking-Read-On-Pipes-Or-Files.html +# +# Used, hopefully, with permission ;) +# +# An non-blocking filehandle read that returns an array of lines read +# Returns: ($eof,@lines) +my %nonblockGetLines_lastSISPM; +sub nonblockGetLinesSISPM { + my ($fh,$timeout) = @_; + + $timeout = 0 unless defined $timeout; + my $rfd = ''; + $nonblockGetLines_lastSISPM{$fh} = '' + unless defined $nonblockGetLines_lastSISPM{$fh}; + + vec($rfd,fileno($fh),1) = 1; + return unless select($rfd, undef, undef, $timeout)>=0; + # I'm not sure the following is necessary? + return unless vec($rfd,fileno($fh),1); + my $buf = ''; + my $n = sysread($fh,$buf,1024*1024); + # If we're done, make sure to send the last unfinished line + return (1,$nonblockGetLines_lastSISPM{$fh}) unless $n; + # Prepend the last unfinished line + $buf = $nonblockGetLines_lastSISPM{$fh}.$buf; + # And save any newly unfinished lines + $nonblockGetLines_lastSISPM{$fh} = + (substr($buf,-1) !~ /[\r\n]/ && $buf =~ s/([^\r\n]*)$//) + ? $1 : ''; + $buf ? (0,split(/\n/,$buf)) : (0); +} + + +1; diff --git a/docs/commandref.html b/docs/commandref.html index 11ed57c52..6de4bcb23 100644 --- a/docs/commandref.html +++ b/docs/commandref.html @@ -80,6 +80,8 @@ structure   WS2000   WS3600   + SISPM   + SIS_PMS   WS300   Weather   USF1000   @@ -3454,6 +3456,168 @@ Forecast Cloudy
+ +

SISPM

+
    +
    + + + Define +
      + define <name> SISPM </path/to/sispmctl> +

      + + PLEASE NOTE: This module is to be considered alpha quality; it has not been + tested extensively, especially the interaction between "set" commands and the sheduled + status reading needs more obervation. (Testing with FIFOs seems as if it's working + without blocking nor interference, but that's on a mostly unloaded, fast system.)

      + BE CAREFUL when using multiple PMs on one host; the parser in SISPM_Define() is MOST + PROBABLY broken here, I haven't had the opportunity to test this yet. YOU HAVE BEEN + WARNED.
      And now to what's this disclaimer tries to prevent you from using ;)

      + + Defines a path to the program "sispmctl", which is used to control (locally attached) + "Silver Shield Power Manager" devices. Usually these are connected to the local computer + via USB, more than one "sispm" device per computer is supported. (Please note that, due + to neglections in their USB driver, AVM's Fritz!Box 7170 (and derivates, like Deutsche + Telekom's Speedport W901V) is not able to talk to these devices ...) + + The communication between FHEM and the Power Manager device is done by using the open + source sispmctl program. Thus, for the + time being, THIS functionality is only available running FHEM on Linux (or any other platform + where you can get the sispmctl program compiled and running). On the bright side: by + interfacing via commandline, it is possible to define multiple SISPM devices, e. g. with + a wrapper that does execute sispmctl on a remote (Linux) system. And: sispmctl runs happily + on Marvells SheevaPlug ;) + + After defining a SISPM device, a first test is done, identifying attached PMs. If this + succeeds, an internal task is scheduled to read the status every 30 seconds. (Reason + being that someone else could have switched sockets externally to FHEM.) + + To actually control any power sockets, you need to define a SIS_PMS + device ;) If autocreate is enabled, those should be autocreated for your convenience as + soon as the first scan took place (30 seconds after the define). + + Implementation of SISPM.pm tries to be nice, that is it reads from the pipe only + non-blocking (== if there is data), so it should be safe even to use it via ssh or + a netcat-pipe over the Internet, but this, as well, has not been tested extensively yet. +

      + + Attributes: +
        +
      • model: SISPM (ignored for now)
      • +
      +
      + Example: +
        + define PMS_Terrarium SISPM /usr/bin/sispmctl
        +
      +
      +
    + + + Set
      N/A

    + + + Get
      N/A

    + + Attributes + +
    +
+ + + +

SIS_PMS

+
    + This module is responsible for handling the actual sockets (power on, + power off, toggle) on a "Silver Shield Power Manager", see SISPM + for how to define access to one (SIS_PMS stands for "Silver Shield Power Manager Socket"). +

    + + + Define +
      + define <name> SIS_PMS <serial> <socket> +

      + + To securely distinguish multiple attached Power Manager devices, the + serial number of those is used. You get these with "sispmctl -s" - or + just let autocreate define the sockets attached for you.
      + +
        +
      • <serial> is the serial number of the Power Manager device, see above.
      • +
      • <socket> is a number between 1 and 4 (for a 4 socket model)
      • +
      +
      + + Examples: +
        + define lamp SIS_PMS 01:02:03:04:05 1
        + define otherlamp SIS_PMS 01:02:03:04:05 3
        + define tv SIS_PMS 01:01:38:44:55 1 +
      +
    +
    + + + Set +
      + set <name> <value> [<time>] +

      + where value is one of:
      +
      +    off
      +    on
      +    toggle
      +    
      + Examples: +
        + set lamp on
        + set lamp1,lamp2,lamp3 on
        + set lamp1-lamp3 on
        +
      +
      + Notes: +
        +
      • As an external program is used, a noticeable delay may occur.
      +
    +
    + + Get
      N/A

    + + + Attributes +
      +
    • do_not_notify

    • + +
    • dummy
      + Set the device attribute dummy to define devices which should not + output any signals. Associated notifys will be executed if the signal + is received. Used e.g. to react to a code from a sender, but it will + not actually switch if triggered in the web frontend. +

    • + +
    • loglevel

    • +
    +
+

IPWE