mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-03 04:36:36 +00:00
Prerelease version. Documentation needed.
git-svn-id: https://svn.fhem.de/fhem/trunk@1762 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
a33630fcbd
commit
a0c21c4647
@ -1,6 +1,8 @@
|
||||
##############################################
|
||||
# $Id: 00_ZWDongle.pm 1721 2012-07-11 14:48:24Z rudolfkoenig $
|
||||
# Handling a ZWave (USB) Dongle
|
||||
# TODO:
|
||||
# - get list of command classes for a device.
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
@ -19,23 +21,28 @@ sub ZWDongle_Write($$$);
|
||||
# http://open-zwave.googlecode.com/svn-history/r426/trunk/cpp/src/Driver.cpp
|
||||
# http://buzzdavidson.com/?p=68
|
||||
my %sets = (
|
||||
"learnMode" => "50%02x", # ZW_SET_LEARN_MODE
|
||||
"sendData" => "13%02x%s", # ZW_SEND_DATA
|
||||
# 032001000500/032001FF0500 (off/on)
|
||||
"addNode" => { cmd => "4a%02x@", # ZW_ADD_NODE_TO_NETWORK',
|
||||
param => {on=>0x81, off=>0x05 } },
|
||||
"removeNode"=> { cmd => "4b%02x@", # ZW_REMOVE_NODE_FROM_NETWORK',
|
||||
param => {on=>0x81, off=>0x05 } },
|
||||
"createNode"=> { cmd => "60%02x" }, # ZW_REQUEST_NODE_INFO',
|
||||
);
|
||||
|
||||
my %gets = (
|
||||
"caps" => "07", # SERIAL_API_GET_CAPABILITIES
|
||||
"ctrlCaps" => "05", # ZW_GET_CONTROLLER_CAPS
|
||||
"devInfo" => "41%02x", # ZW_GET_NODE_PROTOCOL_INFO
|
||||
"devList" => "02", # SERIAL_API_GET_INIT_DATA
|
||||
"nodeInfo" => "41%02x", # ZW_GET_NODE_PROTOCOL_INFO
|
||||
"nodeList" => "02", # SERIAL_API_GET_INIT_DATA
|
||||
"homeId" => "20", # MEMORY_GET_ID
|
||||
"version" => "15", # ZW_GET_VERSION
|
||||
"raw" => "%s",
|
||||
);
|
||||
|
||||
# Known controller function.
|
||||
# Note: Known != implemented, see %sets & %gets for the implemented ones.
|
||||
my %func_id= (
|
||||
use vars qw(%zw_func_id);
|
||||
use vars qw(%zw_type6);
|
||||
%zw_func_id= (
|
||||
'02' => 'SERIAL_API_GET_INIT_DATA',
|
||||
'03' => 'SERIAL_API_APPL_NODE_INFORMATION',
|
||||
'04' => 'APPLICATION_COMMAND_HANDLER',
|
||||
@ -114,6 +121,20 @@ my %func_id= (
|
||||
'd0' => 'ZW_SET_PROMISCUOUS_MODE',
|
||||
);
|
||||
|
||||
%zw_type6 = (
|
||||
'01' => 'GENERIC_CONTROLLER', '12' => 'SWITCH_REMOTE',
|
||||
'02' => 'STATIC_CONTROLLER', '13' => 'SWITCH_TOGGLE',
|
||||
'03' => 'AV_CONTROL_POINT', '20' => 'SENSOR_BINARY',
|
||||
'06' => 'DISPLAY', '21' => 'SENSOR_MULTILEVEL',
|
||||
'07' => 'GARAGE_DOOR', '22' => 'WATER_CONTROL',
|
||||
'08' => 'THERMOSTAT', '30' => 'METER_PULSE',
|
||||
'09' => 'WINDOW_COVERING', '40' => 'ENTRY_CONTROL',
|
||||
'0F' => 'REPEATER_SLAVE', '50' => 'SEMI_INTEROPERABLE',
|
||||
'10' => 'SWITCH_BINARY', 'ff' => 'NON_INTEROPERABLE',
|
||||
'11' => 'SWITCH_MULTILEVEL',
|
||||
);
|
||||
|
||||
|
||||
|
||||
sub
|
||||
ZWDongle_Initialize($)
|
||||
@ -126,6 +147,7 @@ ZWDongle_Initialize($)
|
||||
$hash->{ReadFn} = "ZWDongle_Read";
|
||||
$hash->{WriteFn} = "ZWDongle_Write";
|
||||
$hash->{ReadyFn} = "ZWDongle_Ready";
|
||||
$hash->{ReadAnswerFn} = "ZWDongle_ReadAnswer";
|
||||
|
||||
# Normal devices
|
||||
$hash->{DefFn} = "ZWDongle_Define";
|
||||
@ -156,7 +178,7 @@ ZWDongle_Define($$)
|
||||
my $dev = $a[2];
|
||||
|
||||
$hash->{Clients} = ":ZWave:";
|
||||
my %matchList = ( "1:ZWave" => "^........ ...*" );
|
||||
my %matchList = ( "1:ZWave" => ".*" );
|
||||
$hash->{MatchList} = \%matchList;
|
||||
|
||||
if($dev eq "none") {
|
||||
@ -170,6 +192,7 @@ ZWDongle_Define($$)
|
||||
}
|
||||
|
||||
$hash->{DeviceName} = $dev;
|
||||
$hash->{CallbackNr} = 0;
|
||||
my $ret = DevIo_OpenDev($hash, 0, "ZWDongle_DoInit");
|
||||
return $ret;
|
||||
}
|
||||
@ -185,12 +208,32 @@ ZWDongle_Set($@)
|
||||
return "\"set ZWDongle\" needs at least one parameter" if(@a < 1);
|
||||
my $type = shift @a;
|
||||
|
||||
return "Unknown argument $type, choose one of " . join(" ", sort keys %sets)
|
||||
if(!defined($sets{$type}));
|
||||
my $nargs = int(split("%", $sets{$type}, -1))-1;
|
||||
if(!defined($sets{$type})) {
|
||||
my @r;
|
||||
map { my $p = $sets{$_}{param};
|
||||
push @r,($p ? "$_:".join(",",sort keys %{$p}) : $_)} sort keys %sets;
|
||||
return "Unknown argument $type, choose one of " . join(" ",@r);
|
||||
}
|
||||
my $cmd = $sets{$type}{cmd};
|
||||
my $par = $sets{$type}{param};
|
||||
if($par) {
|
||||
return "Unknown argument for $type, choose one of ".join(" ",keys %{$par})
|
||||
if(!defined($par->{$a[0]}));
|
||||
$a[0] = $par->{$a[0]};
|
||||
}
|
||||
|
||||
if($cmd =~ m/\@/) {
|
||||
my $c = $hash->{CallbackNr}+1;
|
||||
$c = 1 if($c > 255);
|
||||
$hash->{CallbackNr} = $c;
|
||||
$c = sprintf("%02x", $c);
|
||||
$cmd =~ s/\@/$c/g;
|
||||
}
|
||||
|
||||
my $nargs = int(split("%", $cmd, -1))-1;
|
||||
return "set $name $type needs $nargs arguments" if($nargs != int(@a));
|
||||
|
||||
ZWDongle_Write($hash, "00", sprintf($sets{$type}, @a));
|
||||
ZWDongle_Write($hash, "00", sprintf($cmd, @a));
|
||||
return undef;
|
||||
}
|
||||
|
||||
@ -215,13 +258,14 @@ ZWDongle_Get($@)
|
||||
|
||||
ZWDongle_Write($hash, "00", sprintf($gets{$type}, @a));
|
||||
my $re = "^01".substr($gets{$type},0,2); # Start with <01><len><01><CMD>
|
||||
my $ret = ZWDongle_ReadAnswer($hash, "get $name $type", $re);
|
||||
my ($err, $ret) = ZWDongle_ReadAnswer($hash, "get $name $type", $re);
|
||||
return $err if($err);
|
||||
|
||||
my $msg="";
|
||||
$msg = $ret if($ret);
|
||||
my @r = map { ord($_) } split("", pack('H*', $ret)) if(defined($ret));
|
||||
|
||||
if($type eq "devList") { ############################
|
||||
if($type eq "nodeList") { ############################
|
||||
return "$name: Bogus data received" if(int(@r) != 36);
|
||||
my @list;
|
||||
for my $byte (0..28) {
|
||||
@ -241,7 +285,7 @@ ZWDongle_Get($@)
|
||||
for my $byte (0..31) {
|
||||
my $bits = $r[10+$byte];
|
||||
for my $bit (0..7) {
|
||||
my $fn = $func_id{sprintf("%02x", $byte*8+$bit)};
|
||||
my $fn = $zw_func_id{sprintf("%02x", $byte*8+$bit)};
|
||||
push @list, $fn if(($bits & (1<<$bit)) && $fn);
|
||||
}
|
||||
}
|
||||
@ -250,7 +294,7 @@ ZWDongle_Get($@)
|
||||
} elsif($type eq "homeId") { ############################
|
||||
$msg = sprintf("HomeId:%s CtrlNodeId:%s",
|
||||
substr($ret,4,8), substr($ret,12,2));
|
||||
$hash->{HomeId} = substr($ret,4,8);
|
||||
$hash->{homeId} = substr($ret,4,8);
|
||||
|
||||
} elsif($type eq "version") { ############################
|
||||
$msg = join("", map { chr($_) } @r[2..13]);
|
||||
@ -267,28 +311,15 @@ ZWDongle_Get($@)
|
||||
}
|
||||
$msg = join(" ", @list);
|
||||
|
||||
} elsif($type eq "devInfo") { ############################
|
||||
} elsif($type eq "nodeInfo") { ############################
|
||||
my $id = sprintf("%02x", $r[6]);
|
||||
if($id eq "00") {
|
||||
$msg = "node $a[0] is not present";
|
||||
} else {
|
||||
my @list;
|
||||
my @type5 = qw( CONTROLLER STATIC_CONTROLLER SLAVE ROUTING_SLAVE);
|
||||
my %type6 = (
|
||||
'01' => 'GENERIC_CONTROLLER', '12' => 'SWITCH_REMOTE',
|
||||
'02' => 'STATIC_CONTROLLER', '13' => 'SWITCH_TOGGLE',
|
||||
'03' => 'AV_CONTROL_POINT', '20' => 'SENSOR_BINARY',
|
||||
'06' => 'DISPLAY', '21' => 'SENSOR_MULTILEVEL',
|
||||
'07' => 'GARAGE_DOOR', '22' => 'WATER_CONTROL',
|
||||
'08' => 'THERMOSTAT', '30' => 'METER_PULSE',
|
||||
'09' => 'WINDOW_COVERING', '40' => 'ENTRY_CONTROL',
|
||||
'0F' => 'REPEATER_SLAVE', '50' => 'SEMI_INTEROPERABLE',
|
||||
'10' => 'SWITCH_BINARY', 'ff' => 'NON_INTEROPERABLE',
|
||||
'11' => 'SWITCH_MULTILEVEL',
|
||||
);
|
||||
|
||||
push @list, $type5[$r[5]-1] if($r[5]>0 && $r[5] <= @type5);
|
||||
push @list, $type6{$id} if($type6{$id});
|
||||
push @list, $zw_type6{$id} if($zw_type6{$id});
|
||||
push @list, ($r[2] & 0x80) ? "listening" : "sleeping";
|
||||
push @list, "routing" if($r[2] & 0x40);
|
||||
push @list, "40kBaud" if(($r[2] & 0x38) == 0x10);
|
||||
@ -374,8 +405,8 @@ ZWDongle_Read($@)
|
||||
|
||||
$buf = unpack('H*', $buf);
|
||||
|
||||
# The dongle looses data over USB(?), and dropping the old buffer after a
|
||||
# timeout is my only idea of solving this problem.
|
||||
# The dongle looses data over USB for some commands(?), and dropping the old
|
||||
# buffer after a timeout is my only idea of solving this problem.
|
||||
my $ts = gettimeofday();
|
||||
my $data = ($hash->{READ_TS} && $ts-$hash->{READ_TS} > 1) ?
|
||||
"" : $hash->{PARTIAL};
|
||||
@ -420,6 +451,7 @@ ZWDongle_Read($@)
|
||||
Log $ll5, "ZWDongle_Read $name: $msg";
|
||||
last if(defined($local) && (!defined($regexp) || ($msg =~ m/$regexp/)));
|
||||
ZWDongle_Parse($hash, $name, $msg);
|
||||
$msg = undef;
|
||||
}
|
||||
$hash->{PARTIAL} = $data;
|
||||
return $msg if(defined($local));
|
||||
@ -432,9 +464,8 @@ sub
|
||||
ZWDongle_ReadAnswer($$$)
|
||||
{
|
||||
my ($hash, $arg, $regexp) = @_;
|
||||
return ("No FD", undef)
|
||||
return ("No FD (dummy device?)", undef)
|
||||
if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
|
||||
|
||||
my $to = ($hash->{RA_Timeout} ? $hash->{RA_Timeout} : 3);
|
||||
|
||||
for(;;) {
|
||||
@ -467,7 +498,7 @@ ZWDongle_ReadAnswer($$$)
|
||||
}
|
||||
|
||||
my $ret = ZWDongle_Read($hash, $buf, $regexp);
|
||||
return $ret if(defined($ret));
|
||||
return (undef, $ret) if(defined($ret));
|
||||
}
|
||||
|
||||
}
|
||||
@ -482,13 +513,7 @@ ZWDongle_Parse($$$)
|
||||
$hash->{RAWMSG} = $rmsg;
|
||||
|
||||
my %addvals = (RAWMSG => $rmsg);
|
||||
my $homeId = $hash->{HomeId};
|
||||
if(!$homeId) {
|
||||
Log 1, "ERROR: $name HomeId is not set!" if(!$hash->{errReported});
|
||||
$hash->{errReported} = 1;
|
||||
return;
|
||||
}
|
||||
Dispatch($hash, "$homeId $rmsg", \%addvals);
|
||||
Dispatch($hash, $rmsg, \%addvals);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,176 +1,470 @@
|
||||
##############################################
|
||||
# See ZWDongle.pm for inspiration
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub ZWave_Parse($$@);
|
||||
sub ZWave_Set($@);
|
||||
sub ZWave_Get($@);
|
||||
sub ZWave_Cmd($$@);
|
||||
sub ZWave_ParseMeter($);
|
||||
sub ZWave_SetClasses($$$$);
|
||||
|
||||
use vars qw(%zw_func_id);
|
||||
|
||||
my @zwave_models = qw(
|
||||
Ever
|
||||
Everspring_AN1582 Everspring_AN1583
|
||||
);
|
||||
|
||||
sub
|
||||
my %zwave_id2class;
|
||||
my %zwave_class = (
|
||||
NO_OPERATION => { id => '00', },
|
||||
BASIC => { id => '20',
|
||||
set => { basicValue => "01%02x", },
|
||||
get => { basicStatus => "02", },
|
||||
parse => { "..2003(.*)" => '"basicReport:$1"',}, },
|
||||
CONTROLLER_REPLICATION => { id => '21', },
|
||||
APPLICATION_STATUS => { id => '22', },
|
||||
ZIP_SERVICES => { id => '23', },
|
||||
ZIP_SERVER => { id => '24', },
|
||||
SWITCH_BINARY => { id => '25',
|
||||
set => { off => "0100",
|
||||
on => "01FF",
|
||||
reportOn => "03FF",
|
||||
reportOff => "0300", },
|
||||
get => { swbStatus => "02", },
|
||||
parse => { "03250300" => "state:off",
|
||||
"032503ff" => "state:on", }, } ,
|
||||
SWITCH_MULTILEVEL => { id => '26', },
|
||||
SWITCH_ALL => { id => '27', },
|
||||
SWITCH_TOGGLE_BINARY => { id => '28', },
|
||||
SWITCH_TOGGLE_MULTILEVEL => { id => '29', },
|
||||
CHIMNEY_FAN => { id => '2A', },
|
||||
SCENE_ACTUATOR_CONF => { id => '2C', },
|
||||
SCENE_CONTROLLER_CONF => { id => '2D', },
|
||||
ZIP_ADV_SERVICES => { id => '2F', },
|
||||
SCENE_ACTIVATION => { id => '2b', },
|
||||
ZIP_CLIENT => { id => '2e', },
|
||||
SENSOR_BINARY => { id => '30',
|
||||
get => { sbStatus => "02", },
|
||||
parse => { "03300300" => "state:closed",
|
||||
"033003ff" => "state:open", },},
|
||||
SENSOR_MULTILEVEL => { id => '31', },
|
||||
METER => { id => '32',
|
||||
parse => { "..3202(.*)"=> 'ZWave_ParseMeter($1)' }, },
|
||||
ZIP_ADV_SERVER => { id => '33', },
|
||||
ZIP_ADV_CLIENT => { id => '34', },
|
||||
METER_PULSE => { id => '35', },
|
||||
THERMOSTAT_HEATING => { id => '38', },
|
||||
THERMOSTAT_MODE => { id => '40', },
|
||||
THERMOSTAT_OPERATING_STATE => { id => '42', },
|
||||
THERMOSTAT_SETPOINT => { id => '43', },
|
||||
THERMOSTAT_FAN_MODE => { id => '44', },
|
||||
THERMOSTAT_FAN_STATE => { id => '45', },
|
||||
CLIMATE_CONTROL_SCHEDULE => { id => '46', },
|
||||
THERMOSTAT_SETBACK => { id => '47', },
|
||||
BASIC_WINDOW_COVERING => { id => '50', },
|
||||
MTP_WINDOW_COVERING => { id => '51', },
|
||||
MULTI_INSTANCE => { id => '60', },
|
||||
DOOR_LOCK => { id => '62', },
|
||||
USER_CODE => { id => '63', },
|
||||
CONFIGURATION => { id => '70',
|
||||
set => { configDefault=>"04%02x80",
|
||||
configByte => "04%02x01%02x",
|
||||
configWord => "04%02x02%04x",
|
||||
configLong => "04%02x04%08x", },
|
||||
get => { config => "05%02x", },
|
||||
parse => { "..7006(..)..(.*)" => '"config_$1:".hex($2)',}, },
|
||||
ALARM => { id => '71',
|
||||
get => { alarm => "04%02x", },
|
||||
parse => { "..7105(..)(..)" => '"alarm_type_$1:level $2"',}, },
|
||||
MANUFACTURER_SPECIFIC => { id => '72', },
|
||||
POWERLEVEL => { id => '73', },
|
||||
PROTECTION => { id => '75', },
|
||||
LOCK => { id => '76', },
|
||||
NODE_NAMING => { id => '77', },
|
||||
GROUPING_NAME => { id => '7B', },
|
||||
FIRMWARE_UPDATE_MD => { id => '7a', },
|
||||
REMOTE_ASSOCIATION_ACTIVATE => { id => '7c', },
|
||||
REMOTE_ASSOCIATION => { id => '7d', },
|
||||
BATTERY => { id => '80',
|
||||
get => { battery => "02" },
|
||||
parse => { "038003(..)"=> '"battery:".hex($1)." %"' }, },
|
||||
CLOCK => { id => '81', },
|
||||
HAIL => { id => '82', },
|
||||
WAKE_UP => { id => '84',
|
||||
set => { wakeupInterval => "04%06x%02x" },
|
||||
get => { wakeupInterval => "05" },
|
||||
parse => { "028407" => 'wakeup:notification',
|
||||
"..8406(......)(..)" =>
|
||||
'"wakeupReport:interval:".hex($1)." target:".hex($2)',}, },
|
||||
ASSOCIATION => { id => '85',
|
||||
set => { associationAdd => "01%02x%02x*",
|
||||
associationDel => "04%02x%02x*", },
|
||||
get => { association => "02%02x", },
|
||||
parse => { "..8503(..)(..)..(.*)" => '"assocGroup_$1:Max:$2 Nodes:$3"',}, },
|
||||
VERSION => { id => '86',
|
||||
get => { version => "11", },
|
||||
parse => { "078612(..)(..)(..)(..)(..)" =>
|
||||
'sprintf("Lib:%d Prot:%d.%d App:%d.%d",hex($1),hex($2),hex($3),hex($4),hex($5))', } },
|
||||
INDICATOR => { id => '87', },
|
||||
PROPRIETARY => { id => '88', },
|
||||
LANGUAGE => { id => '89', },
|
||||
TIME_PARAMETERS => { id => '8B', },
|
||||
GEOGRAPHIC_LOCATION => { id => '8C', },
|
||||
COMPOSITE => { id => '8D', },
|
||||
MULTI_CMD => { id => '8F', },
|
||||
TIME => { id => '8a', },
|
||||
MULTI_INSTANCE_ASSOCIATION => { id => '8E', },
|
||||
ENERGY_PRODUCTION => { id => '90', },
|
||||
MANUFACTURER_PROPRIETARY => { id => '91', },
|
||||
SCREEN_MD => { id => '92', },
|
||||
SCREEN_ATTRIBUTES => { id => '93', },
|
||||
SIMPLE_AV_CONTROL => { id => '94', },
|
||||
AV_CONTENT_DIRECTORY_MD => { id => '95', },
|
||||
AV_RENDERER_STATUS => { id => '96', },
|
||||
AV_CONTENT_SEARCH_MD => { id => '97', },
|
||||
SECURITY => { id => '98', },
|
||||
AV_TAGGING_MD => { id => '99', },
|
||||
IP_CONFIGURATION => { id => '9A', },
|
||||
ASSOCIATION_COMMAND_CONFIGURATION => { id => '9B', },
|
||||
SENSOR_ALARM => { id => '9C', },
|
||||
SENSOR_CONFIGURATION => { id => '9E', },
|
||||
SILENCE_ALARM => { id => '9d', },
|
||||
MARK => { id => 'ef', },
|
||||
NON_INTEROPERABLE => { id => 'f0', },
|
||||
);
|
||||
|
||||
|
||||
sub
|
||||
ZWave_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
$hash->{Match} = "^........ ...*";
|
||||
$hash->{Match} = ".*";
|
||||
$hash->{SetFn} = "ZWave_Set";
|
||||
$hash->{GetFn} = "ZWave_Get";
|
||||
$hash->{DefFn} = "ZWave_Define";
|
||||
$hash->{UndefFn} = "ZWave_Undef";
|
||||
$hash->{ParseFn} = "ZWave_Parse";
|
||||
$hash->{AttrList} = "IODev do_not_notify:1,0 ".
|
||||
"ignore:1,0 dummy:1,0 showtime:1,0 ".
|
||||
"loglevel:0,1,2,3,4,5,6 " .
|
||||
"model:".join(",", sort @zwave_models);
|
||||
"ignore:1,0 dummy:1,0 showtime:1,0 classes ".
|
||||
"loglevel:0,1,2,3,4,5,6 " .
|
||||
"model:".join(",", sort @zwave_models);
|
||||
map { $zwave_id2class{$zwave_class{$_}{id}} = $_ } keys %zwave_class;
|
||||
}
|
||||
|
||||
my %zwave_classes = (
|
||||
'AV_CONTROL_POINT' => {} ,
|
||||
'DISPLAY' => {} ,
|
||||
'GARAGE_DOOR' => {} ,
|
||||
'THERMOSTAT' => {} ,
|
||||
'WINDOW_COVERING' => {} ,
|
||||
'REPEATER_SLAVE' => {} ,
|
||||
'SWITCH_BINARY' => {
|
||||
set => { "off" => "13%02x0320010005",
|
||||
"on" => "13%02x032001FF05", },
|
||||
parse => { "03250300" => "state:off",
|
||||
"032503ff" => "state:on" }, } ,
|
||||
'SWITCH_MULTILEVEL' => {} ,
|
||||
'SWITCH_REMOTE' => {} ,
|
||||
'SWITCH_TOGGLE' => {} ,
|
||||
'SENSOR_BINARY' => {} ,
|
||||
'SENSOR_MULTILEVEL' => {
|
||||
parse => { "0832022112(....)0000" => '"power:".hex($1)." W"' }, },
|
||||
'WATER_CONTROL' => {} ,
|
||||
'METER_PULSE' => {} ,
|
||||
'ENTRY_CONTROL' => {} ,
|
||||
);
|
||||
|
||||
|
||||
#############################
|
||||
sub
|
||||
sub
|
||||
ZWave_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
my $u = "wrong syntax: define <name> ZWave homeId id class [class...]";
|
||||
return $u if(int(@a) < 4);
|
||||
|
||||
my $name = shift @a;
|
||||
my $type = shift(@a); # always ZWave
|
||||
|
||||
my $u = "wrong syntax for $name: define <name> ZWave homeId id [classes]";
|
||||
return $u if(int(@a) < 2 || int(@a) > 3);
|
||||
|
||||
my $homeId = lc(shift @a);
|
||||
my $id = shift @a;
|
||||
|
||||
return "define $name: wrong homeId ($homeId): need an 8 digit hex value"
|
||||
if( ($homeId !~ m/^[a-f0-9]{8}$/i) );
|
||||
if( ($homeId !~ m/^[a-f0-9]{8}$/i) );
|
||||
return "define $name: wrong id ($id): need a number"
|
||||
if( ($id !~ m/^\d+$/i) );
|
||||
foreach my $cl (@a) {
|
||||
return "define $name: unknown class $cl" if(!$zwave_classes{uc($cl)});
|
||||
}
|
||||
if( ($id !~ m/^\d+$/i) );
|
||||
|
||||
$id = sprintf("%02x", $id);
|
||||
$hash->{HomeId} = $homeId;
|
||||
$hash->{Id} = $id;
|
||||
$hash->{Classes} = uc(join(" ", @a));
|
||||
$hash->{homeId} = $homeId;
|
||||
$hash->{id} = $id;
|
||||
|
||||
$modules{ZWave}{defptr}{"$homeId $id"} = $hash;
|
||||
AssignIoPort($hash); # FIXME: should take homeId into account
|
||||
|
||||
if(@a) {
|
||||
ZWave_SetClasses($homeId, $id, undef, $a[0]);
|
||||
|
||||
if($attr{$name}{classes} =~ m/ASSOCIATION/) {
|
||||
my $iodev = $hash->{IODev};
|
||||
my $homeReading = ReadingsVal($iodev->{NAME}, "homeId", "") if($iodev);
|
||||
my $ctrlId = $1 if($homeReading && $homeReading =~ m/CtrlNodeId:(..)/);
|
||||
|
||||
if($ctrlId) {
|
||||
Log 1, "Adding the controller $ctrlId to association group 1";
|
||||
IOWrite($hash, "00", "130a04850101${ctrlId}05");
|
||||
|
||||
} else {
|
||||
Log 1, "Cannot associate $name, missing controller id";
|
||||
}
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
ZWave_Set($@)
|
||||
ZWave_Cmd($$@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
my ($type, $hash, @a) = @_;
|
||||
my $ret = undef;
|
||||
|
||||
return "no set value specified" if(int(@a) < 2);
|
||||
return "no $type argument specified" if(int(@a) < 2);
|
||||
my $name = shift(@a);
|
||||
my $cmd = shift(@a);
|
||||
|
||||
# Collect the commands from the distinct classes
|
||||
my %cmdList;
|
||||
foreach my $cl (split(" ", $hash->{Classes})) {
|
||||
my $ptr = $zwave_classes{$cl}{set} if($zwave_classes{$cl}{set});
|
||||
my $classes = AttrVal($name, "classes", "");
|
||||
foreach my $cl (split(" ", $classes)) {
|
||||
my $ptr = $zwave_class{$cl}{$type} if($zwave_class{$cl}{$type});
|
||||
next if(!$ptr);
|
||||
foreach my $k (keys %{$ptr}) {
|
||||
$cmdList{$k} = $ptr->{$k};
|
||||
if(!$cmdList{$k}) {
|
||||
$cmdList{$k}{fmt} = $ptr->{$k};
|
||||
$cmdList{$k}{id} = $zwave_class{$cl}{id};
|
||||
}
|
||||
}
|
||||
}
|
||||
return ("Unknown argument $cmd, choose one of ".join(" ",sort keys %cmdList))
|
||||
return ("Unknown $type argument $cmd, choose one of "
|
||||
. join(" ",sort keys %cmdList))
|
||||
if(!$cmdList{$cmd});
|
||||
|
||||
my $cmdOut = sprintf($cmdList{$cmd}, $hash->{Id}, @a);
|
||||
IOWrite($hash, "00", $cmdOut);
|
||||
|
||||
$cmd .= " ".join(" ", @a) if(@a);
|
||||
################################
|
||||
# ZW_SEND_DATA,nodeId,CMD,ACK|AUTO_ROUTE
|
||||
my $id = $hash->{id};
|
||||
my $cmdFmt = $cmdList{$cmd}{fmt};
|
||||
my $cmdId = $cmdList{$cmd}{id};
|
||||
|
||||
my $nArg = 0;
|
||||
$nArg = int(split("%", $cmdFmt))-1 if($cmdFmt =~ m/%/);
|
||||
my $parTxt = ($nArg == 0 ? "no parameter" :
|
||||
($nArg == 1 ? "one parameter" :
|
||||
"$nArg parameters"));
|
||||
if($cmdFmt =~ m/^(.*)\*$/) {
|
||||
$cmdFmt = $1;
|
||||
return "$type $cmd needs at least $parTxt" if($nArg > int(@a));
|
||||
$cmdFmt .= ("%02x" x (int(@a)-$nArg));
|
||||
|
||||
} else {
|
||||
return "$type $cmd needs $parTxt" if($nArg != int(@a));
|
||||
}
|
||||
$cmdFmt = sprintf($cmdFmt, @a) if($nArg);
|
||||
my $len = sprintf("%02x", length($cmdFmt)/2+1);
|
||||
|
||||
my $data = "13$id$len$cmdId${cmdFmt}05";
|
||||
if($classes =~ m/WAKE_UP/) {
|
||||
if(!$hash->{WakeUp}) {
|
||||
my @arr = ();
|
||||
$hash->{WakeUp} = \@arr;
|
||||
}
|
||||
push @{$hash->{WakeUp}}, $data;
|
||||
return ($type eq "get" ? "Scheduled for sending after WAKEUP" : undef);
|
||||
}
|
||||
IOWrite($hash, "00", $data);
|
||||
|
||||
my $val;
|
||||
if($type eq "get") {
|
||||
no strict "refs";
|
||||
my $iohash = $hash->{IODev};
|
||||
my $fn = $modules{$iohash->{TYPE}}{ReadAnswerFn};
|
||||
my ($err, $data) = &{$fn}($iohash, $cmd, "^000400$id");
|
||||
use strict "refs";
|
||||
|
||||
return $err if($err);
|
||||
$val = ZWave_Parse($iohash, $data, 1);
|
||||
|
||||
} else {
|
||||
$cmd .= " ".join(" ", @a) if(@a);
|
||||
|
||||
}
|
||||
|
||||
my $tn = TimeNow();
|
||||
if($type eq "set") {
|
||||
$hash->{CHANGED}[0] = $cmd;
|
||||
$hash->{STATE} = $cmd;
|
||||
$hash->{READINGS}{state}{TIME} = $tn;
|
||||
$hash->{READINGS}{state}{VAL} = $cmd;
|
||||
|
||||
$hash->{CHANGED}[0] = $cmd;
|
||||
$hash->{STATE} = $cmd;
|
||||
$hash->{READINGS}{state}{TIME} = $tn;
|
||||
$hash->{READINGS}{state}{VAL} = $cmd;
|
||||
} else {
|
||||
my $mval = $val;
|
||||
($cmd, $mval) = split(":", $val) if($val);
|
||||
$hash->{READINGS}{$cmd}{TIME} = $tn;
|
||||
$hash->{READINGS}{$cmd}{VAL} = $mval;
|
||||
|
||||
return undef;
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
|
||||
sub ZWave_Set($@) { return ZWave_Cmd("set", shift, @_); }
|
||||
sub ZWave_Get($@) { return ZWave_Cmd("get", shift, @_); }
|
||||
|
||||
|
||||
sub
|
||||
ZWave_Parse($$)
|
||||
ZWave_ParseMeter($)
|
||||
{
|
||||
my ($hash, $msg) = @_;
|
||||
my ($val) = @_;
|
||||
return if($val !~ m/^(..)(..)(.*)$/);
|
||||
my ($v1, $v2, $v3) = (hex($1) & 0x1f, hex($2), $3);
|
||||
my @prectab = (1,10,100,1000,10000,100000,1000000, 10000000);
|
||||
my $prec = $prectab[($v2 >> 5) & 0x7];
|
||||
my $scale = ($v2 >> 3) & 0x3;
|
||||
my $size = ($v2 >> 0) & 0x7;
|
||||
my @txt = ("undef", "power", "gas", "water");
|
||||
my $txt = ($v1 > $#txt ? "undef" : $txt[$v1]);
|
||||
my %unit = (power => ["kWh", "kVAh", "W", "pulseCount"],
|
||||
gas => ["m3", "feet3", "undef", "pulseCount"],
|
||||
water => ["m3", "feet3", "USgallons", "pulseCount"]);
|
||||
my $unit = $txt eq "undef" ? "undef" : $unit{$txt}[$scale];
|
||||
$v3 = hex(substr($v3, 0, 2*$size))/$prec;
|
||||
return "$txt:$v3 $unit";
|
||||
}
|
||||
|
||||
my ($homeId, $pmsg) = split(" ", $msg, 2);
|
||||
return "" if($pmsg !~ m/^000400(..)(.*)$/); # Ignore unknown commands for now
|
||||
my ($id, $p) = ($1, $2);
|
||||
sub
|
||||
ZWave_SetClasses($$$$)
|
||||
{
|
||||
my ($homeId, $id, $type6, $classes) = @_;
|
||||
|
||||
my $def = $modules{ZWave}{defptr}{"$homeId $id"};
|
||||
if($def) {
|
||||
Log 1, "Got $p";
|
||||
if(!$def) {
|
||||
$type6 = $zw_type6{$type6} if($type6 && $zw_type6{$type6});
|
||||
$id = hex($id);
|
||||
return "UNDEFINED ZWave_${type6}_$id ZWave $homeId $id $classes"
|
||||
}
|
||||
|
||||
my @event;
|
||||
my @changed;
|
||||
my $tn = TimeNow();
|
||||
my @classes;
|
||||
for my $classId (grep /../, split(/(..)/, lc($classes))) {
|
||||
push @classes, $zwave_id2class{$classId} if($zwave_id2class{$classId});
|
||||
}
|
||||
my $name = $def->{NAME};
|
||||
$attr{$name}{classes} = join(" ", @classes) if(@classes);
|
||||
$def->{DEF} = "$homeId ".hex($id);
|
||||
return "";
|
||||
}
|
||||
|
||||
foreach my $cl (split(" ", $def->{Classes})) {
|
||||
my $ptr = $zwave_classes{$cl}{parse} if($zwave_classes{$cl}{parse});
|
||||
next if(!$ptr);
|
||||
foreach my $k (keys %{$ptr}) {
|
||||
if($p =~ m/$k/) {
|
||||
my $val = $ptr->{$k};
|
||||
$val = eval $val if(index($val, '$') >= 0);
|
||||
push @event, $val;
|
||||
}
|
||||
}
|
||||
sub
|
||||
ZWave_Parse($$@)
|
||||
{
|
||||
my ($iodev, $msg, $local) = @_;
|
||||
my $homeId = $iodev->{homeId};
|
||||
my $ioName = $iodev->{NAME};
|
||||
if(!$homeId) {
|
||||
Log 1, "ERROR: $ioName homeId is not set!" if(!$iodev->{errReported});
|
||||
$iodev->{errReported} = 1;
|
||||
return;
|
||||
}
|
||||
my $ll4 = AttrVal($ioName, "loglevel", 4);
|
||||
|
||||
return "" if($msg !~ m/00(..)(..)(..)(..*)/); # Ignore unknown commands
|
||||
my ($cmd, $callbackid, $id, $arg) = ($1, $2, $3, $4);
|
||||
$cmd = $zw_func_id{$cmd} if($zw_func_id{$cmd});
|
||||
|
||||
#####################################
|
||||
# Controller commands
|
||||
my $evt;
|
||||
|
||||
if($cmd eq 'ZW_ADD_NODE_TO_NETWORK' ||
|
||||
$cmd eq 'ZW_REMOVE_NODE_FROM_NETWORK') {
|
||||
my @vals = ("learnReady", "nodeFound", "slave",
|
||||
"controller", "", "done", "failed");
|
||||
$evt = ($id eq "00" || hex($id)>@vals+1) ? "unknownArg" : $vals[hex($id)-1];
|
||||
if($evt eq "slave" &&
|
||||
$arg =~ m/(..)....(..)..(.*)$/) {
|
||||
my ($id,$type6,$classes) = ($1, $2, $3);
|
||||
return ZWave_SetClasses($homeId, $id, $type6, $classes);
|
||||
}
|
||||
|
||||
return "" if(!@event);
|
||||
|
||||
for(my $i = 0; $i < int(@event); $i++) {
|
||||
next if($event[$i] eq "");
|
||||
my ($vn, $vv) = split(":", $event[$i], 2);
|
||||
if($vn eq "state") {
|
||||
$def->{STATE} = $vv;
|
||||
push @changed, $vv;
|
||||
|
||||
} else {
|
||||
push @changed, "$vn: $vv";
|
||||
|
||||
}
|
||||
$def->{READINGS}{$vn}{TIME} = $tn;
|
||||
$def->{READINGS}{$vn}{VAL} = $vv;
|
||||
}
|
||||
$def->{CHANGED} = \@changed;
|
||||
return $def->{NAME};
|
||||
|
||||
|
||||
} else {
|
||||
Log 3, "ZWave unknown device $homeId $id, please define it";
|
||||
} elsif($cmd eq "ZW_APPLICATION_UPDATE" && $arg =~ m/....(..)..(.*)$/) {
|
||||
my ($type6,$classes) = ($1, $2, $3);
|
||||
return ZWave_SetClasses($homeId, $id, $type6, $classes);
|
||||
|
||||
}
|
||||
return "";
|
||||
|
||||
if($evt) {
|
||||
return "$cmd $evt" if($local);
|
||||
DoTrigger($ioName, "$cmd $evt");
|
||||
Log $ll4, "$ioName $cmd $evt";
|
||||
return "";
|
||||
|
||||
} else {
|
||||
Log $ll4, "$ioName $cmd $id ($arg)";
|
||||
|
||||
}
|
||||
|
||||
|
||||
######################################
|
||||
# device messages
|
||||
return "" if($cmd ne "APPLICATION_COMMAND_HANDLER" || $arg !~ m/^..(..)/);
|
||||
my $class = $1;
|
||||
my $hash = $modules{ZWave}{defptr}{"$homeId $id"};
|
||||
if(!$hash) {
|
||||
$id = hex($id);
|
||||
Log 3, "Unknown ZWave device $homeId $id, please define it";
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
my $className = $zwave_id2class{$class} ? $zwave_id2class{$class} : "UNKNOWN";
|
||||
my $ptr = $zwave_class{$className}{parse} if($zwave_class{$className}{parse});
|
||||
if(!$ptr) {
|
||||
Log $ll4, "$hash->{NAME}: Unknown message ($className $arg)";
|
||||
return "";
|
||||
}
|
||||
|
||||
my @event;
|
||||
my $wakeup;
|
||||
foreach my $k (keys %{$ptr}) {
|
||||
if($arg =~ m/$k/) {
|
||||
my $val = $ptr->{$k};
|
||||
$val = eval $val if(index($val, '$') >= 0);
|
||||
push @event, $val;
|
||||
Log 1, ">$val<";
|
||||
# $wakeup = 1 if($val eq "wakeup:notification" && !$wakeup);
|
||||
}
|
||||
}
|
||||
|
||||
return "" if(!@event);
|
||||
return join(" ", @event) if($local);
|
||||
|
||||
if($hash->{WakeUp} && @{$hash->{WakeUp}}) {
|
||||
Log 1, "WU...";
|
||||
IOWrite($hash, "00", shift @{$hash->{WakeUp}});
|
||||
}
|
||||
|
||||
|
||||
my @changed;
|
||||
my $tn = TimeNow();
|
||||
for(my $i = 0; $i < int(@event); $i++) {
|
||||
next if($event[$i] eq "");
|
||||
my ($vn, $vv) = split(":", $event[$i], 2);
|
||||
if($vn eq "state") {
|
||||
if($hash->{STATE} ne $vv) {
|
||||
$hash->{STATE} = $vv;
|
||||
push @changed, $vv;
|
||||
}
|
||||
push @changed, "deviceState:$vv";
|
||||
|
||||
} else {
|
||||
push @changed, "$vn: $vv";
|
||||
|
||||
}
|
||||
$hash->{READINGS}{$vn}{TIME} = $tn;
|
||||
$hash->{READINGS}{$vn}{VAL} = $vv;
|
||||
}
|
||||
$hash->{CHANGED} = \@changed;
|
||||
return $hash->{NAME};
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
ZWave_Undef($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $homeId = $hash->{homeId};
|
||||
my $id = $hash->{id};
|
||||
delete $modules{ZWave}{defptr}{"$homeId $id"};
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
||||
|
Loading…
x
Reference in New Issue
Block a user