2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 12:49:34 +00:00

Initial release of support for the Silvershield Power Master USB-controlled

power bars (via sispmctl, see http://sispmctl.sourceforge.net/).


git-svn-id: https://svn.fhem.de/fhem/trunk@549 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
painseeker 2010-01-16 22:08:32 +00:00
parent aad677d2b8
commit 8b5972d361
3 changed files with 720 additions and 0 deletions

198
fhem/FHEM/17_SIS_PMS.pm Executable file
View File

@ -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 <wusel+fhem@uu.org> 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 <name> SIS_PMS <serial> <socket>";
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;

358
fhem/FHEM/70_SISPM.pm Normal file
View File

@ -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 <wusel+fhem@uu.org> 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;

View File

@ -80,6 +80,8 @@
<a href="#structure">structure</a> &nbsp;
<a href="#WS2000">WS2000</a> &nbsp;
<a href="#WS3600">WS3600</a> &nbsp;
<a href="#SISPM">SISPM</a> &nbsp;
<a href="#SIS_PMS">SIS_PMS</a> &nbsp;
<a href="#WS300">WS300</a> &nbsp;
<a href="#Weather">Weather</a> &nbsp;
<a href="#USF1000">USF1000</a> &nbsp;
@ -3454,6 +3456,168 @@ Forecast Cloudy</pre>
<br>
</ul>
<a name="SISPM"></a>
<h3>SISPM</h3>
<ul>
<br>
<a name="SISPMdefine"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; SISPM &lt;/path/to/sispmctl&gt;</code>
<br><br>
<i><b>PLEASE NOTE:</b> 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.)<br><br>
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.</i> And now to what's this disclaimer tries to prevent you from using ;)<br><br>
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) <b>is not</b> able to talk to these devices ...)
The communication between FHEM and the Power Manager device is done by using the open
source <a href="http://sispmctl.sourceforge.net/">sispmctl</a> 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 <a href="#SIS_PMS">SIS_PMS</a>
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.
<br><br>
Attributes:
<ul>
<li><code>model</code>: <code>SISPM</code> (ignored for now)</li>
</ul>
<br>
Example:
<ul>
<code>define PMS_Terrarium SISPM /usr/bin/sispmctl</code><br>
</ul>
<br>
</ul>
<a name="SISPMset"></a>
<b>Set</b> <ul>N/A</ul><br>
<a name="SISPMget"></a>
<b>Get</b> <ul>N/A</ul><br>
<b>Attributes</b>
<ul>
<li><a href="#model">model</a> (SISPM)</li>
</ul>
<br>
</ul>
<a name="SIS_PMS"></a>
<h3>SIS_PMS</h3>
<ul>
This module is responsible for handling the actual sockets (power on,
power off, toggle) on a "Silver Shield Power Manager", see <a href="#SISPM">SISPM</a>
for how to define access to one (SIS_PMS stands for "Silver Shield Power Manager Socket").
<br><br>
<a name="SIS_PMSdefine"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; SIS_PMS &lt;serial&gt; &lt;socket&gt;</code>
<br><br>
To securely distinguish multiple attached Power Manager devices, the
serial number of those is used. You get these with "sispmctl -s"&nbsp;- or
just let autocreate define the sockets attached for you.<br>
<ul>
<li><code>&lt;serial&gt;</code> is the serial number of the Power Manager device, see above.</li>
<li><code>&lt;socket&gt;</code> is a number between 1 and 4 (for a 4 socket model)</li>
</ul>
<br>
Examples:
<ul>
<code>define lamp SIS_PMS 01:02:03:04:05 1</code><br>
<code>define otherlamp SIS_PMS 01:02:03:04:05 3</code><br>
<code>define tv SIS_PMS 01:01:38:44:55 1</code>
</ul>
</ul>
<br>
<a name="SIS_PMSset"></a>
<b>Set </b>
<ul>
<code>set &lt;name&gt; &lt;value&gt; [&lt;time&gt;]</code>
<br><br>
where <code>value</code> is one of:<br>
<pre>
off
<!-- off-for-timer
--> on
<!-- on-for-timer # see the note
--> toggle
<!-- on-till # Special, see the note
--> </pre>
Examples:
<ul>
<code>set lamp on</code><br>
<code>set lamp1,lamp2,lamp3 on</code><br>
<code>set lamp1-lamp3 on</code><br><!--
<code>set lamp on-for-timer 12</code><br>-->
</ul>
<br>
Notes:
<ul>
<li>As an external program is used, a noticeable delay may occur.</li><!--
<li>The <code>time</code> used in <code>*-for-timer</code> is, unlike
with FS20, in seconds and internally uses "at" statements to schedule
the switching.
<li>on-till requires an absolute time in the "at" format (HH:MM:SS, HH:MM
or { &lt;perl code&gt; }, where the perl-code returns a time
specification).
If the current time is greater than the specified time, then the
command is ignored, else an "on" command is generated, and for the
given "till-time" an off command is scheduleld via the at command.
</li>
--> </ul>
</ul>
<br>
<b>Get</b> <ul>N/A</ul><br>
<a name="SIS_PMSattributes"></a>
<b>Attributes</b>
<ul>
<li><a href="#do_not_notify">do_not_notify</a></li><br>
<a name="attrdummy"></a>
<li>dummy<br>
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.
</li><br>
<li><a href="#loglevel">loglevel</a></li><br>
</ul>
</ul>
<a name="IPWE"></a>
<h3>IPWE</h3>
<ul>