mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-13 17:26:34 +00:00
*** empty log message ***
git-svn-id: https://svn.fhem.de/fhem/trunk@264 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
d2fc6430a9
commit
73c03c294f
666
fhem/FHEM/00_CM11.pm
Executable file
666
fhem/FHEM/00_CM11.pm
Executable file
@ -0,0 +1,666 @@
|
||||
################################################################
|
||||
#
|
||||
# Copyright notice
|
||||
#
|
||||
# (c) 2008 Dr. Boris Neubert (omega@online.de)
|
||||
#
|
||||
# This script 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!
|
||||
#
|
||||
################################################################
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
|
||||
sub CM11_Write($$$);
|
||||
sub CM11_Read($);
|
||||
sub CM11_Ready($$);
|
||||
|
||||
my $msg_pollpc = pack("H*", "5a"); # interface poll signal (CM11->PC)
|
||||
my $msg_pollpcpf = pack("H*", "a5"); # power fail poll signal (CM11->PC)
|
||||
my $msg_pollack = pack("H*", "c3"); # response to poll signal (PC->CM11)
|
||||
my $msg_pollackpf= pack("H*", "fb"); # response to power fail poll signal (PC->CM11)
|
||||
my $msg_txok = pack("H*", "00"); # OK for transmission (PC->CM11)
|
||||
my $msg_ifrdy = pack("H*", "55"); # interface ready (CM11->PC)
|
||||
my $msg_statusrq = pack("H*", "8b"); # status request (PC->CM11)
|
||||
|
||||
my %housecodes_rcv = qw(0110 A 1110 B 0010 C 1010 D
|
||||
0001 E 1001 F 0101 G 1101 H
|
||||
0111 I 1111 J 0011 K 1011 L
|
||||
0000 M 1000 N 0100 O 1100 P);
|
||||
|
||||
my %unitcodes_rcv = qw(0110 1 1110 2 0010 3 1010 4
|
||||
0001 5 1001 6 0101 7 1101 8
|
||||
0111 9 1111 10 0011 11 1011 12
|
||||
0000 13 1000 14 0100 15 1100 16);
|
||||
|
||||
my %functions_rcv = qw(0000 ALL_UNITS_OFF
|
||||
0001 ALL_LIGHTS_ON
|
||||
0010 ON
|
||||
0011 OFF
|
||||
0100 DIM
|
||||
0101 BRIGHT
|
||||
0110 ALL_LIGHTS_OFF
|
||||
0111 EXTENDED_CODE
|
||||
1000 HAIL_REQUEST
|
||||
1001 HAIL_ACK
|
||||
1010 PRESET_DIM1
|
||||
1011 PRESET_DIM2
|
||||
1100 EXTENDED_DATA_TRANSFER
|
||||
1101 STATUS_ON
|
||||
1110 STATUS_OFF
|
||||
1111 STATUS_REQUEST);
|
||||
|
||||
|
||||
my %gets = (
|
||||
"test" => "xxx",
|
||||
);
|
||||
|
||||
my %sets = (
|
||||
"test" => "xxx",
|
||||
);
|
||||
|
||||
my $def;
|
||||
my %msghist; # Used when more than one CUL is attached
|
||||
my $msgcount = 0;
|
||||
|
||||
|
||||
#####################################
|
||||
|
||||
sub
|
||||
CM11_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Provider
|
||||
$hash->{ReadFn} = "CM11_Read";
|
||||
$hash->{WriteFn} = "CM11_Write";
|
||||
$hash->{Clients} = ":X10:";
|
||||
$hash->{ReadyFn} = "CM11_Ready" if ($^O eq 'MSWin32');
|
||||
|
||||
# Normal Devices
|
||||
$hash->{DefFn} = "CM11_Define";
|
||||
$hash->{UndefFn} = "CM11_Undef";
|
||||
# $hash->{GetFn} = "CM11_Get";
|
||||
# $hash->{SetFn} = "CM11_Set";
|
||||
$hash->{StateFn} = "CM11_SetState";
|
||||
$hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 " .
|
||||
"model:CM11 loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
#####################################
|
||||
sub
|
||||
CM11_DoInit($$$)
|
||||
{
|
||||
my ($name,$type,$po) = @_;
|
||||
my @init;
|
||||
|
||||
$po->reset_error();
|
||||
$po->baudrate(4800);
|
||||
$po->databits(8);
|
||||
$po->parity('none');
|
||||
$po->stopbits(1);
|
||||
$po->handshake('none');
|
||||
|
||||
if($type && $type eq "strangetty") {
|
||||
|
||||
# This part is for some Linux kernel versions whih has strange default
|
||||
# settings. Device::SerialPort is nice: if the flag is not defined for your
|
||||
# OS then it will be ignored.
|
||||
$po->stty_icanon(0);
|
||||
#$po->stty_parmrk(0); # The debian standard install does not have it
|
||||
$po->stty_icrnl(0);
|
||||
$po->stty_echoe(0);
|
||||
$po->stty_echok(0);
|
||||
$po->stty_echoctl(0);
|
||||
|
||||
# Needed for some strange distros
|
||||
$po->stty_echo(0);
|
||||
$po->stty_icanon(0);
|
||||
$po->stty_isig(0);
|
||||
$po->stty_opost(0);
|
||||
$po->stty_icrnl(0);
|
||||
}
|
||||
|
||||
$po->write_settings;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
my $po;
|
||||
$hash->{STATE} = "Initialized";
|
||||
|
||||
delete $hash->{PortObj};
|
||||
delete $hash->{FD};
|
||||
|
||||
my $name = $a[0];
|
||||
my $dev = $a[2];
|
||||
$hash->{ttytype} = $a[3] if($a[3]);
|
||||
|
||||
$attr{$name}{savefirst} = 1;
|
||||
|
||||
if($dev eq "none") {
|
||||
Log 1, "CM11 device is none, commands will be echoed only";
|
||||
$attr{$name}{dummy} = 1;
|
||||
return undef;
|
||||
}
|
||||
|
||||
Log 3, "CM11 opening CM11 device $dev";
|
||||
if ($^O=~/Win/) {
|
||||
require Win32::SerialPort;
|
||||
$po = new Win32::SerialPort ($dev);
|
||||
} else {
|
||||
require Device::SerialPort;
|
||||
$po = new Device::SerialPort ($dev);
|
||||
}
|
||||
return "Can't open $dev: $!\n" if(!$po);
|
||||
Log 3, "CM11 opened CM11 device $dev";
|
||||
|
||||
$hash->{PortObj} = $po;
|
||||
if( $^O !~ /Win/ ) {
|
||||
$hash->{FD} = $po->FILENO;
|
||||
$selectlist{"$name.$dev"} = $hash;
|
||||
} else {
|
||||
$readyfnlist{"$name.$dev"} = $hash;
|
||||
}
|
||||
$hash->{DeviceName} = $dev;
|
||||
$hash->{PARTIAL} = "";
|
||||
|
||||
CM11_DoInit($name, $hash->{ttytype}, $po);
|
||||
|
||||
#CM11_SetInterfaceTime($hash);
|
||||
#CM11_GetInterfaceStatus($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Undef($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
foreach my $d (sort keys %defs) {
|
||||
if(defined($defs{$d}) &&
|
||||
defined($defs{$d}{IODev}) &&
|
||||
$defs{$d}{IODev} == $hash)
|
||||
{
|
||||
Log GetLogLevel($name,2), "deleting port for $d";
|
||||
delete $defs{$d}{IODev};
|
||||
}
|
||||
}
|
||||
$hash->{PortObj}->close() if($hash->{PortObj});
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_SetState($$$$)
|
||||
{
|
||||
my ($hash, $tim, $vt, $val) = @_;
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_LogReadWrite($@)
|
||||
{
|
||||
my ($rw,$hash, $msg, $trlr) = @_;
|
||||
my $name= $hash->{NAME};
|
||||
Log GetLogLevel($name,5),
|
||||
"CM11 device " . $name . ": $rw " .
|
||||
sprintf("%2d: ", length($msg)) . unpack("H*", $msg);
|
||||
}
|
||||
|
||||
sub
|
||||
CM11_LogRead(@)
|
||||
{
|
||||
CM11_LogReadWrite("read ", @_);
|
||||
}
|
||||
|
||||
sub
|
||||
CM11_LogWrite(@)
|
||||
{
|
||||
CM11_LogReadWrite("write", @_);
|
||||
}
|
||||
|
||||
#####################################
|
||||
|
||||
sub
|
||||
CM11_SimpleWrite($$)
|
||||
{
|
||||
my ($hash, $msg) = @_;
|
||||
return if(!$hash || !defined($hash->{PortObj}));
|
||||
CM11_LogWrite($hash,$msg);
|
||||
$hash->{PortObj}->write($msg);
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_ReadDirect($$)
|
||||
{
|
||||
# This is a direct read for CM11_Write
|
||||
my ($hash,$arg) = @_;
|
||||
return undef if(!$hash || !defined($hash->{FD}));
|
||||
|
||||
my $name= $hash->{NAME};
|
||||
my $prefix= "CM11 device " . $name . ":";
|
||||
my $rin= '';
|
||||
my $nfound;
|
||||
|
||||
if($^O eq 'MSWin32') {
|
||||
$nfound= CM11_Ready($hash, undef);
|
||||
} else {
|
||||
vec($rin, $hash->{FD}, 1) = 1;
|
||||
my $to = 20; # seconds timeout (response might be damn slow)
|
||||
$to = $hash->{RA_Timeout} if($hash->{RA_Timeout}); # ...or less
|
||||
$nfound = select($rin, undef, undef, $to);
|
||||
if($nfound < 0) {
|
||||
next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
|
||||
Log GetLogLevel($name,3), "$prefix Select error $nfound / $!";
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
if(!$nfound) {
|
||||
Log GetLogLevel($name,3), "$prefix Timeout reading $arg";
|
||||
return undef;
|
||||
}
|
||||
|
||||
my $buf = $hash->{PortObj}->input();
|
||||
CM11_LogRead($hash,$buf);
|
||||
return $buf;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Write($$$)
|
||||
{
|
||||
# send two bytes, verify checksum, send ok
|
||||
my ($hash,$b1,$b2) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $prefix= "CM11 device $name:";
|
||||
|
||||
if(!$hash || !defined($hash->{PortObj})) {
|
||||
Log GetLogLevel($name,3),
|
||||
"$prefix device is not active, cannot send";
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
# checksum
|
||||
my $b1d = unpack('C', $b1);
|
||||
my $b2d = unpack('C', $b2);
|
||||
my $checksum_w = ($b1d + $b2d) & 0xff;
|
||||
|
||||
my $data;
|
||||
|
||||
# try 5 times to send
|
||||
my $try= 5;
|
||||
for(;;) {
|
||||
$try--;
|
||||
# send two bytes
|
||||
$data= $b1 . $b2;
|
||||
CM11_LogWrite($hash,$data);
|
||||
$hash->{PortObj}->write($data);
|
||||
|
||||
# get checksum
|
||||
my $checksum= CM11_ReadDirect($hash, "checksum");
|
||||
return 0 if(!defined($checksum)); # read failure
|
||||
|
||||
my $checksum_r= unpack('C', $checksum);
|
||||
if($checksum_w ne $checksum_r) {
|
||||
Log 5,
|
||||
"$prefix wrong checksum (send: $checksum_w, received: $checksum_r)";
|
||||
return 0 if(!$try);
|
||||
my $nexttry= 6-$try;
|
||||
Log 5,
|
||||
"$prefix retrying (" . $nexttry . "/5)";
|
||||
} else {
|
||||
Log 5, "$prefix checksum correct, OK for transmission";
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# checksum ok => send OK for transmission
|
||||
$data= $msg_txok;
|
||||
CM11_LogWrite($hash,$data);
|
||||
$hash->{PortObj}->write($data);
|
||||
my $ready= CM11_ReadDirect($hash, "ready");
|
||||
return 0 if(!defined($ready)); # read failure
|
||||
if($ready ne $msg_ifrdy) {
|
||||
Log GetLogLevel($name,3),
|
||||
"$prefix strange ready signal (" . unpack('C', $ready) . ")";
|
||||
return 0
|
||||
} else {
|
||||
Log 5, "$prefix ready";
|
||||
}
|
||||
|
||||
# we are fine
|
||||
return 1;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_GetInterfaceStatus($)
|
||||
{
|
||||
my ($hash)= @_;
|
||||
|
||||
CM11_SimpleWrite($hash, $msg_statusrq);
|
||||
my $statusmsg= "";
|
||||
while(length($statusmsg)<14) {
|
||||
my $buf= CM11_ReadDirect($hash, "status");
|
||||
return if(!defined($buf)); # read error
|
||||
$statusmsg.= $buf;
|
||||
}
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_SetInterfaceTime($)
|
||||
{
|
||||
my ($hash)= @_;
|
||||
|
||||
# 7 Bytes, Bits 0..55 are
|
||||
# 55 to 48 timer download header (0x9b)
|
||||
# 47 to 40 Current time (seconds)
|
||||
# 39 to 32 Current time (minutes ranging from 0 to 119)
|
||||
# 31 to 23 Current time (hours/2, ranging from 0 to 11)
|
||||
# 23 to 16 Current year day (bits 0 to 7)
|
||||
# 15 Current year day (bit 8)
|
||||
# 14 to 8 Day mask (SMTWTFS)
|
||||
# 7 to 4 Monitored house code
|
||||
# 3 Reserved
|
||||
# 2 Battery timer clear flag
|
||||
# 1 Monitored status clear flag
|
||||
# 0 Timer purge flag
|
||||
|
||||
# make the interface happy (time is set to zero)
|
||||
my $data = pack('C7', 0x9b,0x00,0x00,0x00,0x00,0x00,0x03);
|
||||
CM11_SimpleWrite($hash, $data);
|
||||
# get checksum (ignored)
|
||||
my $checksum= CM11_ReadDirect($hash, "checksum");
|
||||
return 0 if(!defined($checksum)); # read failure
|
||||
# tx OK
|
||||
CM11_SimpleWrite($hash, $msg_txok);
|
||||
# get ready (ignored)
|
||||
my $ready= CM11_ReadDirect($hash, "ready");
|
||||
return 0 if(!defined($ready)); # read failure
|
||||
return 1;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Dispatch($$$$)
|
||||
{
|
||||
my ($hash,$housecode,$unitcodes,$x10func)= @_;
|
||||
|
||||
my $prefix= "CM11 device " . $hash->{NAME} . ":";
|
||||
my $iohash = $modules{$hash->{TYPE}}; # Our (CM11) module pointer
|
||||
|
||||
$unitcodes= "" unless(defined($unitcodes));
|
||||
my $dmsg= "X10:$housecode;$unitcodes;$x10func";
|
||||
Log 5, "$prefix dispatch $dmsg";
|
||||
|
||||
my @found;
|
||||
my $last_module;
|
||||
my $nfound;
|
||||
foreach my $m (sort { $modules{$a}{ORDER} cmp $modules{$b}{ORDER} }
|
||||
grep {defined($modules{$_}{ORDER});}keys %modules) {
|
||||
next if($iohash->{Clients} !~ m/:$m:/);
|
||||
|
||||
# Module is not loaded or the message is not for this module
|
||||
next if(!$modules{$m}{Match} || $dmsg !~ m/$modules{$m}{Match}/i);
|
||||
|
||||
no strict "refs";
|
||||
@found = &{$modules{$m}{ParseFn}}($hash,$dmsg);
|
||||
use strict "refs";
|
||||
$last_module = $m;
|
||||
$nfound= int(@found);
|
||||
last if($nfound);
|
||||
}
|
||||
# if the function was not evaluated, undef was returned
|
||||
if(!$nfound) {
|
||||
Log 1, "Unknown message $dmsg, help me!";
|
||||
return;
|
||||
}
|
||||
|
||||
foreach my $found (@found) {
|
||||
if($found =~ m/^(UNDEFINED) ([^ ]*) (.*)$/) {
|
||||
# The trigger needs a device: we create a minimal temporary one
|
||||
my $d = $1;
|
||||
$defs{$d}{NAME} = $1;
|
||||
$defs{$d}{TYPE} = $last_module;
|
||||
DoTrigger($d, "$2 $3");
|
||||
CommandDelete(undef, $d); # Remove the device
|
||||
$nfound--;
|
||||
} else {
|
||||
DoTrigger($found, undef);
|
||||
}
|
||||
}
|
||||
|
||||
Log 5, "$prefix $nfound devices addressed";
|
||||
return @found;
|
||||
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Read($)
|
||||
{
|
||||
#
|
||||
# prolog
|
||||
#
|
||||
|
||||
my ($hash) = @_;
|
||||
|
||||
my $buf = $hash->{PortObj}->input();
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
# prefix for logging
|
||||
my $prefix= "CM11 device " . $name . ":";
|
||||
|
||||
# Lets' try again: Some drivers return len(0) on the first read...
|
||||
if(defined($buf) && length($buf) == 0) {
|
||||
$buf = $hash->{PortObj}->input();
|
||||
}
|
||||
|
||||
# USB troubleshooting
|
||||
if(!defined($buf) || length($buf) == 0) {
|
||||
my $devname = $hash->{DeviceName};
|
||||
Log 1, "USB device $devname disconnected, waiting to reappear";
|
||||
$hash->{PortObj}->close();
|
||||
for(;;) {
|
||||
sleep(5);
|
||||
if ($^O eq 'MSWin32') {
|
||||
$hash->{PortObj} = new Win32::SerialPort($devname);
|
||||
}else{
|
||||
$hash->{PortObj} = new Device::SerialPort($devname);
|
||||
}
|
||||
if($hash->{PortObj}) {
|
||||
Log 1, "USB device $devname reappeared";
|
||||
$hash->{FD} = $hash->{PortObj}->FILENO if !($^O eq 'MSWin32');
|
||||
CM11_DoInit($name, $hash->{ttytype}, $hash->{PortObj});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# begin of message digesting
|
||||
#
|
||||
|
||||
# concatenate yet unparsed message and newly received data
|
||||
my $x10data = $hash->{PARTIAL} . $buf;
|
||||
CM11_LogRead($hash,$buf);
|
||||
Log 5, "$prefix Data: " . unpack('H*',$x10data);
|
||||
|
||||
# normally the while loop will run only once
|
||||
while(length($x10data) > 0) {
|
||||
|
||||
# we cut off everything before the latest poll signal
|
||||
my $p= index(reverse($x10data), $msg_pollpc);
|
||||
if($p<0) { $p= index(reverse($x10data), $msg_pollpcpf); }
|
||||
if($p>=0) { $x10data= substr($x10data, -$p-1); }
|
||||
|
||||
# to start with, a single 0x5a is received
|
||||
if( substr($x10data,0,1) eq $msg_pollpc ) { # CM11 polls PC
|
||||
Log 5, "$prefix start of message";
|
||||
CM11_SimpleWrite($hash, $msg_pollack); # PC ready
|
||||
$x10data= substr($x10data,1); # $x10data now empty
|
||||
next;
|
||||
}
|
||||
|
||||
# experimental code follows
|
||||
#if( substr($x10data,0,2) eq pack("H*", "98e6") ) { # CM11 polls PC
|
||||
# Log 5, "$prefix 98e6";
|
||||
# CM11_SimpleWrite($hash, $msg_pollack); # PC ready
|
||||
# $x10data= "";
|
||||
# next;
|
||||
#}
|
||||
#if( substr($x10data,0,1) eq pack("H*", "98") ) { # CM11 polls PC
|
||||
# Log 5, "$prefix 98";
|
||||
# next;
|
||||
#}
|
||||
|
||||
# a single 0xa5 is a power-fail macro download poll
|
||||
if( substr($x10data,0,1) eq $msg_pollpcpf ) { # CM11 polls PC
|
||||
Log 5, "$prefix power-fail poll";
|
||||
# the documentation wrongly says that the macros should be downloaded
|
||||
# in fact, the time must be set!
|
||||
if(CM11_SetInterfaceTime($hash)) {
|
||||
Log 5, "$prefix power-fail poll satisfied";
|
||||
} else {
|
||||
Log 5, "$prefix power-fail poll satisfaction failed";
|
||||
}
|
||||
$x10data= substr($x10data,1); # $x10data now empty
|
||||
next;
|
||||
}
|
||||
|
||||
# a single 0x55 is a leftover from a failed transmission
|
||||
if( substr($x10data,0,1) eq $msg_ifrdy ) { # CM11 polls PC
|
||||
Log 5, "$prefix skipping leftover ready signal";
|
||||
$x10data= substr($x10data,1);
|
||||
next;
|
||||
}
|
||||
|
||||
# the message comes in small chunks of 1 or few bytes instead of the
|
||||
# whole buffer at once
|
||||
my $len= ord(substr($x10data,0,1))-1; # upload buffer size
|
||||
last if(length($x10data)< $len+2); # wait for complete msg
|
||||
|
||||
# message is now complete, start interpretation
|
||||
|
||||
# mask: Bits 0 (LSB)..7 (MSB) correspond to data bytes 0..7
|
||||
# bit= 0: unitcode, bit= 1: function
|
||||
my $mask= unpack('B8', substr($x10data,1,1));
|
||||
$x10data= substr($x10data,2); # cut off length and mask
|
||||
|
||||
# $x10data now contains $len data bytes
|
||||
my $databytes= unpack('H*', substr($x10data,0));
|
||||
Log 5, "$prefix message complete " .
|
||||
"(length $len, mask $mask, data $databytes)";
|
||||
|
||||
# the following lines decode the messages into unitcodes and functions
|
||||
# in general we have 0..n unitcodes followed by 1..m functions in the
|
||||
# message
|
||||
my $i= 0;
|
||||
my $dmsg= "";
|
||||
while($i< $len) {
|
||||
|
||||
my $data= substr($x10data, $i);
|
||||
my $bits = unpack('B8', $data);
|
||||
my $nibble_hi = substr($bits, 0, 4);
|
||||
my $nibble_lo = substr($bits, 4, 4);
|
||||
|
||||
my $housecode= $housecodes_rcv{$nibble_hi};
|
||||
|
||||
# one hash for unitcodes X_UNIT and one hash for functions
|
||||
# X_FUNC is maintained per housecode X= A..P
|
||||
my $housecode_unit= $housecode . "_UNIT";
|
||||
my $housecode_func= $housecode . "_FUNC";
|
||||
|
||||
my $isfunc= (substr($mask, -$i-1, 1));
|
||||
if($isfunc) {
|
||||
# data byte is function
|
||||
my $x10func= $functions_rcv{$nibble_lo};
|
||||
if(($x10func eq "DIM") || ($x10func eq "BRIGHT")) {
|
||||
my $level= ord(substr($x10data, ++$i));
|
||||
$x10func.= " $level";
|
||||
}
|
||||
elsif($x10func eq "EXTENDED_DATA_TRANSFER") {
|
||||
$data= substr($x10data, 2+(++$i));
|
||||
my $command= substr($x10data, ++$i);
|
||||
$x10func.= unpack("H*", $data) . ":" .
|
||||
unpack("H*", $command);
|
||||
}
|
||||
$hash->{$housecode_func}= $x10func;
|
||||
Log 5, "$prefix $housecode_func: " .
|
||||
$hash->{$housecode_func};
|
||||
# dispatch message to clients
|
||||
CM11_Dispatch($hash, $housecode,
|
||||
$hash->{$housecode_unit},
|
||||
$hash->{$housecode_func});
|
||||
} else {
|
||||
# data byte is unitcode
|
||||
# if a command was executed before, clear unitcode list
|
||||
if(defined($hash->{$housecode_func})) {
|
||||
undef $hash->{$housecode_unit};
|
||||
undef $hash->{$housecode_func};
|
||||
}
|
||||
# get unitcode of unitcode
|
||||
my $unitcode= $unitcodes_rcv{$nibble_lo};
|
||||
# append to list of unitcodes
|
||||
my $unitcodes= $hash->{$housecode_unit};
|
||||
if(defined($hash->{$housecode_unit})) {
|
||||
$unitcodes= $hash->{$housecode_unit} . " ";
|
||||
} else {
|
||||
$unitcodes= "";
|
||||
}
|
||||
$hash->{$housecode_unit}= "$unitcodes$unitcode";
|
||||
Log 5, "$prefix $housecode_unit: " .
|
||||
$hash->{$housecode_unit};
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$x10data= '';
|
||||
}
|
||||
|
||||
$hash->{PARTIAL} = $x10data;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Ready($$) # Windows - only
|
||||
{
|
||||
my ($hash, $dev) = @_;
|
||||
my $po=$hash->{PortObj};
|
||||
return undef if !$po;
|
||||
my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=$po->status;
|
||||
return ($InBytes>0);
|
||||
}
|
||||
|
||||
1;
|
371
fhem/FHEM/20_X10.pm
Executable file
371
fhem/FHEM/20_X10.pm
Executable file
@ -0,0 +1,371 @@
|
||||
################################################################
|
||||
#
|
||||
# Copyright notice
|
||||
#
|
||||
# (c) 2008 Dr. Boris Neubert (omega@online.de)
|
||||
#
|
||||
# This script 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!
|
||||
#
|
||||
################################################################
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my %functions = ( ALL_UNITS_OFF => "all_units_off",
|
||||
ALL_LIGHTS_ON => "all_lights_on",
|
||||
ON => "on",
|
||||
OFF => "off",
|
||||
DIM => "dimdown",
|
||||
BRIGHT => "dimup",
|
||||
ALL_LIGHTS_OFF => "all_lights_off",
|
||||
EXTENDED_CODE => "",
|
||||
HAIL_REQUEST => "",
|
||||
HAIL_ACK => "",
|
||||
PRESET_DIM1 => "",
|
||||
PRESET_DIM2 => "",
|
||||
EXTENDED_DATA_TRANSFER => "",
|
||||
STATUS_ON => "",
|
||||
STATUS_OFF => "",
|
||||
STATUS_REQUEST => "",
|
||||
);
|
||||
|
||||
my %snoitcnuf; # the reverse of the above
|
||||
|
||||
my %functions_rewrite = ( "all_units_off" => "off",
|
||||
"all_lights_on" => "on",
|
||||
"all_lights_off" => "off",
|
||||
);
|
||||
|
||||
my %functions_snd = qw( ON 0010
|
||||
OFF 0011
|
||||
DIM 0100
|
||||
BRIGHT 0101 );
|
||||
|
||||
my %housecodes_snd = qw(A 0110 B 1110 C 0010 D 1010
|
||||
E 0001 F 1001 G 0101 H 1101
|
||||
I 0111 J 1111 K 0011 K 1011
|
||||
M 0000 N 1000 O 0100 P 1100);
|
||||
|
||||
my %unitcodes_snd = qw( 1 0110 2 1110 3 0010 4 1010
|
||||
5 0001 6 1001 7 0101 8 1101
|
||||
9 0111 10 1111 11 0011 12 1011
|
||||
13 0000 14 1000 15 0100 16 1100);
|
||||
|
||||
|
||||
my %functions_set = ( "on" => 0,
|
||||
"off" => 0,
|
||||
"dimup" => 1,
|
||||
"dimdown" => 1,
|
||||
"on-till" => 1,
|
||||
);
|
||||
|
||||
# devices{HOUSE}{UNIT} -> Pointer to hash for the device for lookups
|
||||
my %devices;
|
||||
|
||||
my %models = (
|
||||
lm12 => 'dimmer',
|
||||
lm15 => 'simple',
|
||||
am12 => 'simple',
|
||||
tm13 => 'simple',
|
||||
);
|
||||
|
||||
my @lampmodules = ('lm12','lm15'); # lamp modules
|
||||
|
||||
|
||||
sub
|
||||
X10_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
foreach my $k (keys %functions) {
|
||||
$snoitcnuf{$functions{$k}}= $k;
|
||||
}
|
||||
|
||||
$hash->{Match} = "^X10:[A-P];";
|
||||
$hash->{SetFn} = "X10_Set";
|
||||
$hash->{StateFn} = "X10_SetState";
|
||||
$hash->{DefFn} = "X10_Define";
|
||||
$hash->{UndefFn} = "X10_Undef";
|
||||
$hash->{ParseFn} = "X10_Parse";
|
||||
$hash->{AttrList} = "follow-on-for-timer:1,0 do_not_notify:1,0 dummy:1,0
|
||||
showtime:1,0 model:lm12,lm15,am12,tm13 loglevel:0,1,2,3,4,5,6";
|
||||
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
X10_SetState($$$$)
|
||||
{
|
||||
my ($hash, $tim, $vt, $val) = @_;
|
||||
return undef;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
X10_Do_On_Till($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "Timespec (HH:MM[:SS]) needed for the on-till command" if(@a != 3);
|
||||
|
||||
my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[2]);
|
||||
return $err if($err);
|
||||
|
||||
my @lt = localtime;
|
||||
my $hms_till = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
|
||||
my $hms_now = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
|
||||
if($hms_now ge $hms_till) {
|
||||
Log 4, "on-till: won't switch as now ($hms_now) is later than $hms_till";
|
||||
return "";
|
||||
}
|
||||
|
||||
my @b = ($a[0], "on");
|
||||
X10_Set($hash, @b);
|
||||
CommandDefine(undef, $hash->{NAME} . "_till at $hms_till set $a[0] off");
|
||||
|
||||
}
|
||||
|
||||
###################################
|
||||
|
||||
sub
|
||||
X11_Write($$$)
|
||||
{
|
||||
my ($hash, $function, $dim)= @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $housecode= $hash->{HOUSE};
|
||||
my $unitcode = $hash->{UNIT};
|
||||
my $x10func = $snoitcnuf{$function};
|
||||
undef $function; # do not use after this point
|
||||
my $prefix= "X10 device $name:";
|
||||
|
||||
Log 5, "$prefix sending X10:$housecode;$unitcode;$x10func $dim";
|
||||
|
||||
my ($hc_b, $hu_b, $hf_b);
|
||||
my ($hc, $hu, $hf);
|
||||
|
||||
# Header:Code, Address
|
||||
$hc_b = "00000100"; # 0x04
|
||||
$hc = pack("B8", $hc_b);
|
||||
$hu_b = $housecodes_snd{$housecode} . $unitcodes_snd{$unitcode};
|
||||
$hu = pack("B8", $hu_b);
|
||||
IOWrite($hash, $hc, $hu);
|
||||
|
||||
# Header:Code, Function
|
||||
$hc_b = substr(unpack('B8', pack('C', $dim)), 3) . # dim, 0..22
|
||||
"110"; # always 110
|
||||
$hc = pack("B8", $hc_b);
|
||||
$hf_b = $housecodes_snd{$housecode} . $functions_snd{$x10func};
|
||||
$hf = pack("B8", $hf_b);
|
||||
IOWrite($hash, $hc, $hf);
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
X10_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
my $ret = undef;
|
||||
my $na = int(@a);
|
||||
|
||||
# initialization and sanity checks
|
||||
return "no set value specified" if($na < 2);
|
||||
|
||||
my $name= $hash->{NAME};
|
||||
my $function= $a[1];
|
||||
my $nrparams= $functions_set{$function};
|
||||
return "Unknown argument $function, choose one of " .
|
||||
join(",", sort keys %functions_set) if(!defined($nrparams));
|
||||
return "Wrong number of parameters" if($na != 2+$nrparams);
|
||||
|
||||
# special for on-till
|
||||
return X10_Do_On_Till($hash, @a) if($function eq "on-till");
|
||||
|
||||
# argument evaluation
|
||||
my $model= $hash->{MODEL};
|
||||
|
||||
my $dim= 0;
|
||||
if($function =~ m/^dim/) {
|
||||
return "Cannot dim $name (model $model)" if($models{$model} ne "dimmer");
|
||||
my $arg= $a[2];
|
||||
return "Wrong argument $arg, use 0..22" if($arg !~ m/^[0-9]{1,2}$/);
|
||||
return "Wrong argument $arg, use 0..22" if($arg>22);
|
||||
$dim= $arg;
|
||||
}
|
||||
|
||||
# send command to CM11
|
||||
X11_Write($hash, $function, $dim) if(!IsDummy($a[0]));
|
||||
|
||||
my $v = join(" ", @a);
|
||||
Log GetLogLevel($a[0],2), "X10 set $v";
|
||||
(undef, $v) = split(" ", $v, 2); # Not interested in the name...
|
||||
|
||||
my $tn = TimeNow();
|
||||
|
||||
$hash->{CHANGED}[0] = $v;
|
||||
$hash->{STATE} = $v;
|
||||
$hash->{READINGS}{state}{TIME} = $tn;
|
||||
$hash->{READINGS}{state}{VAL} = $v;
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
X10_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> X10 model housecode unitcode"
|
||||
if(int(@a)!= 5);
|
||||
|
||||
my $model= $a[2];
|
||||
return "Define $a[0]: wrong model: specify one of " .
|
||||
join ",", sort keys %models
|
||||
if(!grep { $_ eq $model} keys %models);
|
||||
|
||||
my $housecode = $a[3];
|
||||
return "Define $a[0]: wrong housecode format: specify a value ".
|
||||
"from A to P"
|
||||
if($housecode !~ m/^[A-P]$/i);
|
||||
|
||||
my $unitcode = $a[4];
|
||||
return "Define $a[0]: wrong unitcode format: specify a value " .
|
||||
"from 1 to 16"
|
||||
if( ($unitcode<1) || ($unitcode>16) );
|
||||
|
||||
|
||||
$hash->{MODEL} = $model;
|
||||
$hash->{HOUSE} = $housecode;
|
||||
$hash->{UNIT} = $unitcode;
|
||||
|
||||
if(defined($devices{$housecode}{$unitcode})) {
|
||||
return "Error: duplicate X10 device $housecode $unitcode definition " .
|
||||
$hash->{NAME} . " (previous: " .
|
||||
$devices{$housecode}{$unitcode}->{NAME} .")";
|
||||
}
|
||||
|
||||
$devices{$housecode}{$unitcode}= $hash;
|
||||
|
||||
AssignIoPort($hash);
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
X10_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
if( defined($hash->{HOUSE}) && defined($hash->{UNIT}) ) {
|
||||
delete($devices{$hash->{HOUSE}}{$hash->{UNIT}});
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
X10_Parse($$)
|
||||
{
|
||||
my ($hash, $msg) = @_;
|
||||
|
||||
# message example: X10:N;1 12;OFF
|
||||
(undef, $msg)= split /:/, $msg, 2; # strip off "X10"
|
||||
my ($housecode,$unitcodes,$command)= split /;/, $msg, 4;
|
||||
|
||||
my @list; # list of selected devices
|
||||
|
||||
#
|
||||
# command evaluation
|
||||
#
|
||||
my ($x10func,$arg)= split / /, $command, 2;
|
||||
my $function= $functions{$x10func}; # translate, eg BRIGHT -> dimup
|
||||
undef $x10func; # do not use after this point
|
||||
|
||||
# the following code sequence converts an all on/off command into
|
||||
# a sequence of simple on/off commands for all defined devices
|
||||
my $all_lights= ($function=~ m/^all_lights_/);
|
||||
my $all_units= ($function=~ m/^all_units_/);
|
||||
if($all_lights || $all_units) {
|
||||
$function= $functions_rewrite{$function}; # translate, all_lights_on -> on
|
||||
$unitcodes= "";
|
||||
foreach my $unitcode (keys %{ $devices{$housecode} } ) {
|
||||
my $h= $devices{$housecode}{$unitcode};
|
||||
my $islampmodule= grep { $_ eq $h->{MODEL} } @lampmodules;
|
||||
if($all_units || $islampmodule ) {
|
||||
$unitcodes.= " " if($unitcodes ne "");
|
||||
$unitcodes.= $h->{UNIT};
|
||||
}
|
||||
}
|
||||
# no units for that housecode
|
||||
if($unitcodes eq "") {
|
||||
Log 3, "X10 No units with housecode $housecode, command $command, " .
|
||||
"please define one";
|
||||
push(@list,
|
||||
"UNDEFINED X10 device $housecode ?, command $command");
|
||||
return @list;
|
||||
}
|
||||
}
|
||||
|
||||
# apply to each unit in turn
|
||||
my @unitcodes= split / /, $unitcodes;
|
||||
|
||||
if(!int(@unitcodes)) {
|
||||
# command without unitcodes, this happens when a single on/off is sent
|
||||
# but no unit was previously selected
|
||||
Log 3, "X10 No unit selected for housecode $housecode, command $command";
|
||||
push(@list,
|
||||
"UNDEFINED X10 device $housecode ?, command $command");
|
||||
return @list;
|
||||
}
|
||||
|
||||
# function rewriting
|
||||
my $value= $function;
|
||||
return @list if($value eq ""); # function not evaluated
|
||||
|
||||
# function determined, add argument
|
||||
if( defined($arg) ) {
|
||||
# received dims from 0..210
|
||||
my $dim= $arg;
|
||||
$value = "$value $dim" ;
|
||||
}
|
||||
|
||||
|
||||
my $unknown_unitcodes= '';
|
||||
foreach my $unitcode (@unitcodes) {
|
||||
my $h= $devices{$housecode}{$unitcode};
|
||||
if($h) {
|
||||
my $name= $h->{NAME};
|
||||
$h->{CHANGED}[0] = $value;
|
||||
$h->{STATE} = $value;
|
||||
$h->{READINGS}{state}{TIME} = TimeNow();
|
||||
$h->{READINGS}{state}{VAL} = $value;
|
||||
Log GetLogLevel($name,2), "X10 $name $value";
|
||||
push(@list, $name);
|
||||
} else {
|
||||
Log 3, "X10 Unknown device $housecode $unitcode, command $command, " .
|
||||
"please define it";
|
||||
push(@list,
|
||||
"UNDEFINED X10 device $housecode $unitcode, command $command");
|
||||
}
|
||||
}
|
||||
return @list;
|
||||
|
||||
}
|
||||
|
||||
|
||||
1;
|
Loading…
x
Reference in New Issue
Block a user