2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-03 16:56:54 +00:00

New module for handling X10 security and X10 light messages

git-svn-id: https://svn.fhem.de/fhem/trunk@829 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
wherzig 2011-02-05 20:30:54 +00:00
parent 154403a07f
commit db42f33b1a

565
fhem/FHEM/43_RFXX10REC.pm Executable file
View File

@ -0,0 +1,565 @@
#################################################################################
# 44_RFXX10REC.pm
# Modul for FHEM for unkown RFXCOM X11security
#
# - X10 security messages for
# - ds10a: X10 Door / Window Sensor or compatible devices
# - ss10a: X10 motion sensor
# - sd90: Marmitek smoke detector
# - kr18: X10 remote control
# - X10 light messages for
# - ms14a: motion sensor
# - x10: generic X10 sensor
#
# 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.
#
##################################
#
# Special thanks to RFXCOM, http://www.rfxcom.com/, for their
# help. I own an USB-RFXCOM-Receiver (433.92MHz, USB, order code 80002)
# and highly recommend it.
#
##################################
#
# Some code from X10security code is derived from http://www.xpl-perl.org.uk/.
# xpl-perl/lib/xPL/RF/X10Security.pm:
# Thanks a lot to Mark Hindess who wrote xPL.
#
#SEE ALSO
# Project website: http://www.xpl-perl.org.uk/
# AUTHOR: Mark Hindess, soft-xpl-perl@temporalanomaly.com
#
#Copyright (C) 2007, 2009 by Mark Hindess
#
#This library is free software; you can redistribute it and/or modify
#it under the same terms as Perl itself, either Perl version 5.8.7 or,
#at your option, any later version of Perl 5 you may have available.
#
##################################
#
# values for "set global verbose"
# 4: log unknown protocols
# 5: log decoding hexlines for debugging
#
package main;
use strict;
use warnings;
use Switch;
# Debug this module? YES = 1, NO = 0
my $RFXX10REC_debug = 0;
my $time_old = 0;
my $RFXX10REC_type_default = "ds10a";
my $RFXX10REC_X10_type_default = "x10";
my $DOT = q{_};
sub
RFXX10REC_Initialize($)
{
my ($hash) = @_;
#$hash->{Match} = "^\\).*"; # 0x29
$hash->{Match} = "^(\\ |\\)).*"; # 0x20 or 0x29
$hash->{DefFn} = "RFXX10REC_Define";
$hash->{UndefFn} = "RFXX10REC_Undef";
$hash->{ParseFn} = "RFXX10REC_Parse";
$hash->{AttrList} = "IODev do_not_notify:1,0 loglevel:0,1,2,3,4,5,6";
#Log 1, "RFXX10REC: Initialize";
}
#####################################
sub
RFXX10REC_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
my $a = int(@a);
if(int(@a) != 5 && int(@a) != 7) {
Log 1,"RFXX10REC wrong syntax '@a'. \nCorrect syntax is 'define <name> RFXX10REC <type> <deviceid> <devicelog> [<deviceid2> <devicelog2>]'";
return "wrong syntax: define <name> RFXX10REC <type> <deviceid> <devicelog> [<deviceid2> <devicelog2>]";
}
my $name = $a[0];
my $type = lc($a[2]);
my $deviceid = $a[3];
my $devicelog = $a[4];
my $device_name = "RFXX10REC".$DOT.$deviceid;
if ($type ne "ds10a" && $type ne "sd90" && $type ne "x10" && $type ne "ms10a" && $type ne "ms14a" && $type ne "kr18") {
Log 1,"RFX10SEC define: wrong type: $type";
return "RFX10SEC: wrong type: $type";
}
$hash->{RFXX10REC_deviceid} = $deviceid;
$hash->{RFXX10REC_devicelog} = $devicelog;
$hash->{RFXX10REC_type} = $type;
#$hash->{RFXX10REC_CODE} = $deviceid;
$modules{RFXX10REC}{defptr}{$device_name} = $hash;
if (int(@a) == 7) {
# there is a second deviceid:
#
my $deviceid2 = $a[5];
my $devicelog2 = $a[6];
my $device_name2 = "RFXX10REC".$DOT.$deviceid2;
$hash->{RFXX10REC_deviceid2} = $deviceid2;
$hash->{RFXX10REC_devicelog2} = $devicelog2;
#$hash->{RFXX10REC_CODE2} = $deviceid2;
$modules{RFXX10REC}{defptr2}{$device_name2} = $hash;
}
AssignIoPort($hash);
return undef;
}
#####################################
sub
RFXX10REC_Undef($$)
{
my ($hash, $name) = @_;
delete($modules{RFXX10REC}{defptr}{$name});
return undef;
}
#####################################
sub RFXX10REC_parse_X10 {
my $bytes = shift;
# checksum test
(($bytes->[0]^0xff) == $bytes->[1] && ($bytes->[2]^0xff) == $bytes->[3])
or return "";
#RFXX10REC_reverse_bits($bytes);
my %x10_devname =
(
0x60 => "A",
0x70 => "B",
0x40 => "C",
0x50 => "D",
0x80 => "E",
0x90 => "F",
0xA0 => "G",
0xB0 => "H",
0xE0 => "I",
0xF0 => "J",
0xC0 => "K",
0xD0 => "L",
0x00 => "M",
0x10 => "N",
0x20 => "O",
0x30 => "P",
);
my $dev_first = "?";
my $devnr = $bytes->[0] & 0xf0;
if (exists $x10_devname{$devnr}) {
$dev_first = $x10_devname{$devnr};
}
my $unit_bit0 = ($bytes->[2] & 0x10) ? 1 : 0;
my $unit_bit1 = ($bytes->[2] & 0x08) ? 1 : 0;
my $unit_bit2 = ($bytes->[2] & 0x40) ? 1 : 0;
my $unit_bit3 = ($bytes->[0] & 0x04) ? 1 : 0;
my $unit = $unit_bit0 * 1 + $unit_bit1 * 2 + $unit_bit2 * 4 +$unit_bit3 * 8 + 1;
my $device = sprintf '%s%0d', $dev_first, $unit;
my $data = $bytes->[2];
my $hexdata = sprintf '%02x', $bytes->[2];
my $error;
if ($data == 0x98) {
$error = "RFXX10REC: X10 command 'Dim' not implemented, device=".$dev_first;
Log 1,$error;
return $error;
} elsif ($data == 0x88) {
$error = "RFXX10REC: X10 command 'Bright' not implemented, device=".$dev_first;
Log 1,$error;
return $error;
} elsif ($data == 0x90) {
$error = "RFXX10REC: X10 command 'All Lights on' not implemented, device=".$dev_first;
Log 1,$error;
return $error;
} elsif ($data == 0x80) {
$error = "RFXX10REC: X10 command 'All Lights off' not implemented, device=".$dev_first;
Log 1,$error;
return $error;
}
my $command;
if ($data & 0x20) {
$command = "off";
} else {
$command = "on";
}
my @res;
my $current = "";
#--------------
my $device_name = "RFXX10REC".$DOT.$device;
#Log 1, "RFXX10REC: device_name=$device_name command=$command" if ($RFXX10REC_debug == 1);
my $firstdevice = 1;
my $def = $modules{RFXX10REC}{defptr}{$device_name};
if(!$def) {
#Log 1, "-1- not device_name=$device_name";
$firstdevice = 0;
$def = $modules{RFXX10REC}{defptr2}{$device_name};
if (!$def) {
#Log 1, "-2- not device_name=$device_name";
Log 3, "RFXX10REC: RFXX10REC Unknown device $device_name, please define it";
return "UNDEFINED $device_name RFXX10REC $RFXX10REC_X10_type_default $device Unknown";
}
}
# Use $def->{NAME}, because the device may be renamed:
my $name = $def->{NAME};
Log 1, "RFXX10REC: $name devn=$device_name first=$firstdevice type=$command, cmd=$hexdata" if ($RFXX10REC_debug == 1);
my $n = 0;
my $tm = TimeNow();
my $val = "";
my $device_type = $def->{RFXX10REC_type};
#Log 1,"device_name=$device_name name=$name, type=$type";
my $sensor = "";
if ($device_type eq "ms14a") {
# for ms14a behave like x11, but flip second deviceid
$device_type = "x10";
if ($firstdevice == 1) {
$command = ($command eq "on") ? "alert" : "normal" ;
} else {
$command = ($command eq "on") ? "off" : "on" ;
}
}
if ($device_type eq "x10") {
$current = $command;
$sensor = $firstdevice == 1 ? $def->{RFXX10REC_devicelog} : $def->{RFXX10REC_devicelog2};
$val .= $current;
$def->{READINGS}{$sensor}{TIME} = $tm;
$def->{READINGS}{$sensor}{VAL} = $current;
$def->{CHANGED}[$n++] = $sensor . ": " . $current;
} else {
Log 1, "RFXX10REC: X10 error unknown sensor type=$device_type $name devn=$device_name first=$firstdevice type=$command, user=$device (hex $hexdata)";
return "RFXX10REC X10 error unknown sensor type=$device_type for $device_name device=$device";
}
if (($firstdevice == 1) && $val) {
$def->{STATE} = $val;
$def->{TIME} = $tm;
$def->{CHANGED}[$n++] = $val;
}
DoTrigger($name, undef);
return "";
}
#####################################
sub RFXX10REC_parse_X10Sec {
my $bytes = shift;
# checksum test
(($bytes->[0]^0x0f) == $bytes->[1] && ($bytes->[2]^0xff) == $bytes->[3])
or return "";
#RFXX10REC_reverse_bits($bytes);
#my $device = sprintf 'x10sec%02x', $bytes->[0];
my $device = sprintf '%02x%02x', $bytes->[4], $bytes->[0];
#Log 1, "X10Sec device-nr=$device";
my $short_device = $bytes->[0];
my $data = $bytes->[2];
my $hexdata = sprintf '%02x', $bytes->[2];
#Log 1, "X10Sec data=$hexdata";
my %x10_security =
(
0x00 => ['X10Sec', 'alert', 'max_delay', 'batt_ok'],
0x01 => ['X10Sec', 'alert', 'max_delay', 'batt_low'],
0x04 => ['X10Sec', 'alert', 'min_delay', 'batt_ok'],
0x05 => ['X10Sec', 'alert', 'min_delay', 'batt_low'],
0x80 => ['X10Sec', 'normal', 'max_delay', 'batt_ok'],
0x81 => ['X10Sec', 'normal', 'max_delay', 'batt_low'],
0x84 => ['X10Sec', 'normal', 'min_delay', 'batt_ok'],
0x85 => ['X10Sec', 'normal', 'min_delay', 'batt_low'],
0x26 => ['X10Sec', 'alert', '', ''],
#
0x0c => ['X10Sec', 'alert', '', 'batt_ok'], # MS10a
0x0d => ['X10Sec', 'alert', '', 'batt_low'], # MS10a
0x8c => ['X10Sec', 'normal', '', 'batt_ok'], # MS10a
0x8d => ['X10Sec', 'normal', '', 'batt_low'], # MS10a
#
0x06 => ['X10Sec', 'Security-Arm', '', ''], # kr18
0x86 => ['X10Sec', 'Security-Disarm', '', ''], # kr18
0x42 => ['X10Sec', 'ButtonA-on', '', ''], # kr18
0xc2 => ['X10Sec', 'ButtonA-off', '', ''], # kr18
0x46 => ['X10Sec', 'ButtonB-on', '', ''], # kr18
0xc6 => ['X10Sec', 'ButtonB-off', '', ''], # kr18
);
my $command = "";
my $type = "";
my $delay = "";
my $battery = "";
my @res;
my %args;
if (exists $x10_security{$data}) {
my $rec = $x10_security{$data};
if (ref $rec) {
($type, $command, $delay, $battery) = @$rec;
} else {
$command = $rec;
}
} else {
Log 1, "RFXX10REC undefined command cmd=$data device-nr=$device, hex=$hexdata";
return "RFXX10REC undefined command";
}
my $current = "";
#--------------
my $device_name = "RFXX10REC".$DOT.$device;
#Log 1, "device_name=$device_name";
Log 4, "device_name=$device_name";
my $firstdevice = 1;
my $def = $modules{RFXX10REC}{defptr}{$device_name};
if(!$def) {
#Log 1, "-1- not device_name=$device_name";
$firstdevice = 0;
$def = $modules{RFXX10REC}{defptr2}{$device_name};
if (!$def) {
#Log 1, "-2- not device_name=$device_name";
Log 3, "RFXX10REC: RFXX10REC Unknown device $device_name, please define it";
return "UNDEFINED $device_name RFXX10REC $RFXX10REC_type_default $device Window";
}
}
# Use $def->{NAME}, because the device may be renamed:
my $name = $def->{NAME};
#Log 1, "name=$new_name";
Log 1, "RFXX10REC: $name devn=$device_name first=$firstdevice type=$command, delay=$delay, batt=$battery cmd=$hexdata" if ($RFXX10REC_debug == 1);
my $n = 0;
my $tm = TimeNow();
my $val = "";
my $device_type = $def->{RFXX10REC_type};
#Log 1,"device_name=$device_name name=$name, type=$type";
my $sensor = "";
if ($device_type eq "ds10a") {
$current = "Error";
$current = "Open" if ($command eq "alert");
$current = "Closed" if ($command eq "normal");
$sensor = $def->{RFXX10REC_devicelog};
$val .= $current;
$def->{READINGS}{$sensor}{TIME} = $tm;
$def->{READINGS}{$sensor}{VAL} = $current;
$def->{CHANGED}[$n++] = $sensor . ": " . $current;
if (($def->{STATE} ne $val)) {
$sensor = "statechange";
$def->{READINGS}{$sensor}{TIME} = $tm;
$def->{READINGS}{$sensor}{VAL} = $current;
$def->{CHANGED}[$n++] = $sensor . ": " . $current;
}
if ($battery ne '') {
$sensor = "battery";
$current = "Error";
$current = "ok" if ($battery eq "batt_ok");
$current = "low" if ($battery eq "batt_low");
$def->{READINGS}{$sensor}{TIME} = $tm;
$def->{READINGS}{$sensor}{VAL} = $current;
$def->{CHANGED}[$n++] = $sensor . ": " . $current;
}
if ($delay ne '') {
$sensor = "delay";
$current = "Error";
$current = "min" if ($delay eq "min_delay");
$current = "max" if ($delay eq "max_delay");
$def->{READINGS}{$sensor}{TIME} = $tm;
$def->{READINGS}{$sensor}{VAL} = $current;
$def->{CHANGED}[$n++] = $sensor . ": " . $current;
}
} elsif ($device_type eq "sd90") {
$sensor = $firstdevice == 1 ? $def->{RFXX10REC_devicelog} : $def->{RFXX10REC_devicelog2};
$current = $command;
if ($firstdevice == 1) {
$val .= $current;
}
$def->{READINGS}{$sensor}{TIME} = $tm;
$def->{READINGS}{$sensor}{VAL} = $current;
$def->{CHANGED}[$n++] = $sensor . ": " . $current;
if ($battery) {
$sensor = "battery";
$current = "Error";
$current = "ok" if ($battery eq "batt_ok");
$current = "low" if ($battery eq "bat_low");
$def->{READINGS}{$sensor}{TIME} = $tm;
$def->{READINGS}{$sensor}{VAL} = $current;
$def->{CHANGED}[$n++] = $sensor . ": " . $current;
}
# sd90 does not have a delay switch
if (0 && $delay) {
$sensor = "delay";
$current = "Error";
$current = "min" if ($delay eq "min_delay");
$current = "max" if ($delay eq "max_delay");
$def->{READINGS}{$sensor}{TIME} = $tm;
$def->{READINGS}{$sensor}{VAL} = $current;
$def->{CHANGED}[$n++] = $sensor . ": " . $current;
}
} elsif ($device_type eq "ms10a") {
$current = $command;
$sensor = $def->{RFXX10REC_devicelog};
$val .= $current;
$def->{READINGS}{$sensor}{TIME} = $tm;
$def->{READINGS}{$sensor}{VAL} = $current;
$def->{CHANGED}[$n++] = $sensor . ": " . $current;
if (($def->{STATE} ne $val)) {
$sensor = "statechange";
$def->{READINGS}{$sensor}{TIME} = $tm;
$def->{READINGS}{$sensor}{VAL} = $current;
$def->{CHANGED}[$n++] = $sensor . ": " . $current;
}
if ($battery ne '') {
$sensor = "battery";
$current = "Error";
$current = "ok" if ($battery eq "batt_ok");
$current = "low" if ($battery eq "batt_low");
$def->{READINGS}{$sensor}{TIME} = $tm;
$def->{READINGS}{$sensor}{VAL} = $current;
$def->{CHANGED}[$n++] = $sensor . ": " . $current;
}
} elsif ($device_type eq "kr18") {
$current = $command;
#$sensor = $def->{RFXX10REC_devicelog};
$val = $current;
#$def->{READINGS}{$sensor}{TIME} = $tm;
#$def->{READINGS}{$sensor}{VAL} = $current;
#$def->{CHANGED}[$n++] = $sensor . ": " . $current;
my @cmd_split = split(/-/, $command);
$sensor = $cmd_split[0];
$current = $cmd_split[1];
$def->{READINGS}{$sensor}{TIME} = $tm;
$def->{READINGS}{$sensor}{VAL} = $current;
$def->{CHANGED}[$n++] = $sensor . ": " . $current;
} else {
Log 1, "RFXX10REC: error unknown sensor type=$device_type $name devn=$device_name first=$firstdevice type=$command, user=$device, delay=$delay, batt=$battery (hex $hexdata)";
return "RFXX10REC error unknown sensor type=$device_type for $device_name device=$device";
}
if (($firstdevice == 1) && $val) {
$def->{STATE} = $val;
$def->{TIME} = $tm;
$def->{CHANGED}[$n++] = $val;
}
DoTrigger($name, undef);
return "";
}
sub
RFXX10REC_Parse($$)
{
my ($hash, $msg) = @_;
my $time = time();
my $hexline = unpack('H*', $msg);
if ($time_old ==0) {
Log 5, "RFXX10REC: decoding delay=0 hex=$hexline";
} else {
my $time_diff = $time - $time_old ;
Log 5, "RFXX10REC: decoding delay=$time_diff hex=$hexline";
}
$time_old = $time;
# convert string to array of bytes. Skip length byte
my @rfxcom_data_array = ();
foreach (split(//, substr($msg,1))) {
push (@rfxcom_data_array, ord($_) );
}
my $bits = ord($msg);
my $num_bytes = $bits >> 3; if (($bits & 0x7) != 0) { $num_bytes++; }
my $res = "";
if ($bits == 41) {
Log 1, "RFXX10REC: bits=$bits num_bytes=$num_bytes hex=$hexline" if ($RFXX10REC_debug == 1);
$res = RFXX10REC_parse_X10Sec(\@rfxcom_data_array);
Log 1, "RFXX10REC: unsupported hex=$hexline" if ($res ne "" && $res !~ /^UNDEFINED.*/);
return $res;
} elsif ($bits == 32) {
Log 1, "RFXX10REC: bits=$bits num_bytes=$num_bytes hex=$hexline" if ($RFXX10REC_debug == 1);
$res = RFXX10REC_parse_X10(\@rfxcom_data_array);
Log 1, "RFXX10REC: unsupported hex=$hexline" if ($res ne "" && $res !~ /^UNDEFINED.*/);
return $res;
} else {
Log 0, "RFXX10REC: bits=$bits num_bytes=$num_bytes hex=$hexline";
}
return "";
}
1;