diff --git a/lib/raZberry.pm b/lib/raZberry.pm index fff510fd8..da017a413 100755 --- a/lib/raZberry.pm +++ b/lib/raZberry.pm @@ -8,9 +8,14 @@ In user code: use raZberry.pm; $razberry_controller = new raZberry('192.168.0.100',1); $razberry_comm = new raZberry_comm($razberry_controller); - $family_room_fan = new raZberry_dimmer($razberry_controller,'2:0:38','force_update'); + $family_room_fan = new raZberry_dimmer($razberry_controller,'2','force_update'); + $family_room_blind = new raZberry_blind($razberry_controller,'3'); + $front_lock = new raZberry_blind($razberry_controller,'4'); + +So far only raZberry_dimmer, raZberry_lock and raZberry_blind are working child objects + +raZberry_(,,{data} = undef; $self->{child_object} = undef; $self->{config}->{poll_seconds} = 5; + $self->{config}->{poll_seconds} = $main::config_parms{raZberry_poll_seconds} + if ( defined $main::config_parms{raZberry_poll_seconds} ); $self->{config}->{poll_seconds} = $poll if ($poll); $self->{config}->{poll_seconds} = 1 if ( $self->{config}->{poll_seconds} < 1 ); $self->{updating} = 0; $self->{data}->{retry} = 0; my ( $host, $port ) = ( split /:/, $addr )[ 0, 1 ]; - $self->{host} = $host; - $self->{port} = 8083; - $self->{port} = $port if ($port); - $self->{debug} = 0; + $self->{host} = $host; + $self->{port} = 8083; + $self->{port} = $port if ($port); + $self->{debug} = 0; + $self->{debug} = $main::Debug{raZberry} + if ( defined $main::Debug{raZberry} ); $self->{lastupdate} = undef; - $self->{timeout} = 5; #300; + $self->{timeout} = 2; + $self->{timeout} = $main::config_parms{raZberry_timeout} + if ( defined $main::config_parms{raZberry_timeout} ); + $self->{status} = ""; $self->{timer} = new Timer; $self->start_timer; @@ -117,24 +161,14 @@ sub poll { &main::print_log( "[raZberry] Forcing update to device $dev to account for local changes" ) if ( $self->{debug} ); - my $cmd; - my ( $devid, $instance, $class ) = ( split /-/, $dev )[ 0, 1, 2 ]; - $cmd = "%5B" - . $devid - . "%5D.instances%5B" - . $instance - . "%5D.commandClasses%5B" - . $class - . "%5D.Get()"; - &main::print_log("cmd=$cmd") if ( $self->{debug} > 1 ); - my ( $isSuccessResponse0, $status ) = - _get_JSON_data( $self, 'force_update', $cmd ); - unless ($isSuccessResponse0) { - &main::print_log( "[raZberry] Error: Problem retrieving data from " - . $self->{host} ); - $self->{data}->{retry}++; - return ('0'); - } + $self->update_dev($dev); + } + + for my $dev ( keys %{ $self->{data}->{ping} } ) { + &main::print_log("[raZberry] Keep_alive: Pinging device $dev...") + ; # if ($self->{debug}); + &main::print_log("ping_dev $dev"); # if ($self->{debug}); + #$self->ping_dev($dev); } my ( $isSuccessResponse1, $devices ) = @@ -153,7 +187,7 @@ sub poll { if ( $self->{debug} ); my ($id) = ( split /_/, $item->{id} )[2]; - #print "id=$id\n"; + #print "id=$id\n" if ($self->{debug} > 1); $self->{data}->{devices}->{$id}->{level} = $item->{metrics}->{level}; $self->{data}->{devices}->{$id}->{updateTime} = $item->{updateTime}; @@ -162,6 +196,7 @@ sub poll { $self->{data}->{devices}->{$id}->{title} = $item->{metrics}->{title}; $self->{data}->{devices}->{$id}->{icon} = $item->{metrics}->{icon}; + $self->{status} = "online"; if ( defined $self->{child_object}->{$id} ) { &main::print_log( @@ -214,6 +249,73 @@ sub set_dev { } +sub ping_dev { + my ( $self, $device ) = @_; + + #curl --globoff "http://mhip:8083/ZWaveAPI/Run/devices[x].SendNoOperation()" + my ( $devid, $instance, $class ) = ( split /-/, $device )[ 0, 1, 2 ]; + &main::print_log("[raZberry] Pinging device $device ($devid)...") + if ( $self->{debug} ); + my $cmd; + $cmd = "%5B" . $devid . "%5D.SendNoOperation()"; + &main::print_log("ping cmd=$cmd"); # if ($self->{debug} > 1); + my ( $isSuccessResponse0, $status ) = _get_JSON_data( $self, 'ping', $cmd ); + unless ($isSuccessResponse0) { + &main::print_log( + "[raZberry] Error: Problem retrieving data from " . $self->{host} ); + $self->{data}->{retry}++; + return ('0'); + } + return ($status); +} + +sub isfailed_dev { + + #"http://192.168.0.155:8083/ZWaveAPI/Run/devices[x].data.isFailed.value" + my ( $self, $device ) = @_; + my ( $devid, $instance, $class ) = ( split /-/, $device )[ 0, 1, 2 ]; + &main::print_log("[raZberry] Checking $device ($devid)...") + if ( $self->{debug} ); + my $cmd; + $cmd = "%5B" . $devid . "%5D.data.isFailed.value"; + &main::print_log("isFailed cmd=$cmd"); # if ($self->{debug} > 1); + my ( $isSuccessResponse0, $status ) = + _get_JSON_data( $self, 'isfailed', $cmd ); + + unless ($isSuccessResponse0) { + &main::print_log( + "[raZberry] Error: Problem retrieving data from " . $self->{host} ); + $self->{data}->{retry}++; + return ('error'); + } + return ($status); +} + +sub update_dev { + my ( $self, $device ) = @_; + my $cmd; + my ( $devid, $instance, $class ) = ( split /-/, $device )[ 0, 1, 2 ]; + $cmd = "%5B" + . $devid + . "%5D.instances%5B" + . $instance + . "%5D.commandClasses%5B" + . $class + . "%5D.Get()"; + &main::print_log("[raZberry] Getting local state from $device ($devid)...") + if ( $self->{debug} ); + &main::print_log("cmd=$cmd") if ( $self->{debug} > 1 ); + my ( $isSuccessResponse0, $status ) = + _get_JSON_data( $self, 'force_update', $cmd ); + unless ($isSuccessResponse0) { + &main::print_log( + "[raZberry] Error: Problem retrieving data from " . $self->{host} ); + $self->{data}->{retry}++; + return ('0'); + } + return ($status); +} + #------------------------------------------------------------------------------------ sub _get_JSON_data { my ( $self, $mode, $cmd ) = @_; @@ -229,7 +331,12 @@ sub _get_JSON_data { my $params = ""; $params = $cmd if ($cmd); my $method = "ZAutomation/api/v1"; - $method = "ZWaveAPI/Run" if ( $mode eq "force_update" ); + $method = "ZWaveAPI/Run" + if ( ( $mode eq "force_update" ) + or ( $mode eq "ping" ) + or ( $mode eq "isfailed" ) + or ( $mode eq "usercode" ) + or ( $mode eq "usercode_data" ) ); &main::print_log( "[raZberry] contacting http://$host:$port/$method/$rest{$mode}$params" ) if ( $self->{debug} ); @@ -253,9 +360,12 @@ sub _get_JSON_data { "[raZberry] Warning, failed to get data. Response code $responseCode" ); if ( defined $self->{child_object}->{comm} ) { - if ( $self->{child_object}->{comm}->state() ne "offline" ) { + if ( $self->{status} eq "online" ) { + $self->{status} = "offline"; main::print_log - "Communication Tracking object found. Updating..." + "Communication Tracking object found. Updating from " + . $self->{child_object}->{comm}->state() + . " to offline..." if ( $self->{loglevel} ); $self->{child_object}->{comm}->set( "offline", 'poll' ); } @@ -263,14 +373,22 @@ sub _get_JSON_data { return ('0'); } if ( defined $self->{child_object}->{comm} ) { - if ( $self->{child_object}->{comm}->state() ne "online" ) { + if ( $self->{status} eq "offline" ) { + $self->{status} = "online"; main::print_log - "Communication Tracking object found. Updating..." + "Communication Tracking object found. Updating from " + . $self->{child_object}->{comm}->state() + . " to online..." if ( $self->{loglevel} ); $self->{child_object}->{comm}->set( "online", 'poll' ); } } - return ('1') if ( $mode eq "force_update" ); + return ('1') + if ( ( $mode eq "force_update" ) + or ( $mode eq "ping" ) + or ( $mode eq "usercode" ) ) + ; #these come backs as nulls which crashes JSON::XS, so just return. + return ( $responseObj->content ) if ( $mode eq "isfailed" ); my $response = JSON::XS->new->decode( $responseObj->content ); return ( $isSuccessResponse, $response ) @@ -340,11 +458,19 @@ sub register { else { &main::print_log("[raZberry] Registering Device ID $dev to controller"); $self->{child_object}->{$dev} = $object; - if ( $options =~ m/force_update/ ) { - $self->{data}->{force_update}->{$dev} = 1; - &main::print_log( - "[raZberry] Forcing Controller to contact Device $dev at each poll" - ); + if ( defined $options ) { + if ( $options =~ m/force_update/ ) { + $self->{data}->{force_update}->{$dev} = 1; + &main::print_log( + "[raZberry] Forcing Controller to contact Device $dev at each poll" + ); + } + if ( $options =~ m/keep_alive/ ) { + $self->{data}->{ping}->{$dev} = 1; + &main::print_log( + "[raZberry] Forcing Controller to ping Device $dev at each poll" + ); + } } } } @@ -365,8 +491,8 @@ sub new { ); $$self{master_object} = $object; - $$self{devid} = $devid; - + $devid = $devid . $zway_suffix unless ( $devid =~ m/-\d+-\d+$/ ); + $$self{devid} = $devid; $object->register( $self, $devid, $options ); #$self->set($object->get_dev_status,$devid,'poll'); @@ -437,11 +563,338 @@ sub level { return ( $self->{level} ); } +sub ping { + my ($self) = @_; + + $$self{master_object}->ping_dev( $$self{devid} ); +} + +sub isfailed { + my ($self) = @_; + + $$self{master_object}->isfailed_dev( $$self{devid} ); +} + +package raZberry_blind; + +#only tested with Somfy ZRTSI module + +@raZberry_blind::ISA = ('Generic_Item'); + +sub new { + my ( $class, $object, $devid, $options ) = @_; + + my $self = {}; + bless $self, $class; + push( @{ $$self{states} }, 'up', 'down', 'stop' ); + + $$self{master_object} = $object; + $devid = $devid . $zway_suffix unless ( $devid =~ m/-\d+-\d+$/ ); + $$self{devid} = $devid; + + $object->register( $self, $devid, $options ); + + #$self->set($object->get_dev_status,$devid,'poll'); + $self->{level} = ""; + $self->{debug} = $object->{debug}; + return $self; + +} + +sub set { + my ( $self, $p_state, $p_setby ) = @_; + + if ( $p_setby eq 'poll' ) { + $self->{level} = $p_state; + my $n_state; + if ( $p_state == 0 ) { + $n_state = "down"; + } + elsif ( $p_state > 0 ) { + $n_state = "up"; + } + + # stop level? + main::print_log( "[raZberry_blind] Setting value to $n_state. Level is " + . $self->{level} ) + if ( $self->{debug} ); + + $self->SUPER::set($n_state); + } + else { + if ( ( lc $p_state eq "up" ) + or ( lc $p_state eq "down" ) + or ( lc $p_state eq "stop" ) ) + { + $$self{master_object}->set_dev( $$self{devid}, $p_state ); + + #} elsif (($p_state eq "100%") or ($p_state =~ m/^\d{1,2}\%$/)) { + # my ($n_state) = ($p_state =~ /(\d+)%/); + # $$self{master_object}->set_dev($$self{devid},"level=$n_state"); + } + else { + main::print_log( + "[raZberry_blind] Error. Unknown set state $p_state"); + } + } +} + +sub level { + my ($self) = @_; + + return ( $self->{level} ); +} + +sub ping { + my ($self) = @_; + + $$self{master_object}->ping_dev( $$self{devid} ); +} + +sub isfailed { + my ($self) = @_; + + $$self{master_object}->isfailed_dev( $$self{devid} ); +} + +package raZberry_lock; + +#only tested with Kwikset 914 + +@raZberry_lock::ISA = ('Generic_Item'); +use Data::Dumper; + +sub new { + my ( $class, $object, $devid, $options ) = @_; + + my $self = {}; + bless $self, $class; + push( @{ $$self{states} }, 'locked', 'unlocked' ); + + $$self{master_object} = $object; + my $devid_battery = $devid . "-0-128"; + $devid = $devid . "-0-98"; + $$self{devid} = $devid; + $$self{devid_battery} = $devid_battery; + + $object->register( $self, $devid, $options ); + $object->register( $self, $devid_battery, $options ); + + #$self->set($object->get_dev_status,$devid,'poll'); + $self->{level} = ""; + $self->{user_data_delay} = 10; + $self->{battery_alert} = 0; + $self->{battery_poll_seconds} = 12 * 60 * 60; + $self->{battery_timer} = new Timer; + $self->{debug} = $object->{debug}; + $self->_battery_timer; + return $self; +} + +sub set { + my ( $self, $p_state, $p_setby ) = @_; + + # if level is open/closed its the state. if level is a number its the battery + # object states are locked and unlocked, but zwave sees close and open + my %map_states; + $map_states{close} = "locked"; + $map_states{open} = "unlocked"; + $map_states{locked} = "close"; + $map_states{unlocked} = "open"; + + if ( $p_setby eq 'poll' ) { + main::print_log( "[raZberry_lock] Setting value to $p_state: " + . $map_states{$p_state} + . ". Level is " + . $self->{level} ) + if ( $self->{debug} ); + if ( ( $p_state eq "open" ) or ( $p_state eq "close" ) ) { + $self->SUPER::set( $map_states{$p_state} ); + } + elsif ( ( $p_state >= 0 ) or ( $p_state <= 100 ) ) { #battery level + $self->{level} = $p_state; + } + else { + main::print_log( + "[raZberry_lock] Unknown value $p_state in poll set"); + } + + } + else { + if ( ( lc $p_state eq "locked" ) or ( lc $p_state eq "unlocked" ) ) { + $$self{master_object} + ->set_dev( $$self{devid}, $map_states{$p_state} ); + } + else { + main::print_log( "[raZberry_lock] Error. Unknown set state " + . $map_states{$p_state} ); + } + } +} + +sub level { + my ($self) = @_; + + return ( $self->{level} ); +} + +sub ping { + my ($self) = @_; + + $$self{master_object}->ping_dev( $$self{devid} ); +} + +sub isfailed { + my ($self) = @_; + + $$self{master_object}->isfailed_dev( $$self{devid} ); +} + +sub battery_check { + my ($self) = @_; + if ( $self->{level} eq "" ) { + main::print_log( + "[raZberry_lock] INFO Battery level currently undefined"); + return; + } + main::print_log( + "[raZberry_lock] INFO Battery currently at " . $self->{level} . "%" ); + if ( ( $self->{level} < 30 ) and ( $self->{battery_alert} == 0 ) ) { + $self->{battery_alert} = 1; + main::speak("Warning, Zwave lock battery has less than 30% charge"); + } + else { + $self->{battery_alert} = 0; + } +} + +sub enable_user { + my ( $self, $userid, $code ) = @_; + my ($status) = 0; + + $status = $self->_control_user( $userid, $code, "1" ); + + #delay for the lock to process the code and then read in the users + main::eval_with_timer( sub { &raZberry_lock::_update_users($self) }, + $self->{user_data_delay} ); + return ($status); +} + +sub disable_user { + my ( $self, $userid ) = @_; + my ($status) = 0; + my $code = "1234"; + main::print_log("[raZberry_lock] WARN user $userid is not in user table") + unless ( defined $self->{users}->{$userid}->{status} ); + $status = $self->_control_user( $userid, $code, "0" ); + + #delay for the lock to process the code and then read in the users + main::eval_with_timer( sub { &raZberry_lock::_update_users($self) }, + $self->{user_data_delay} ); + return ($status); +} + +sub is_user_enabled { + my ( $self, $userid ) = @_; + my $return = 0; + $return = $self->{users}->{$userid}->{status} + if ( defined $self->{users}->{$userid}->{status} ); + return $return; +} + +sub print_users { + my ( $self, $force ) = @_; + + $self->_update_users + unless ( ( defined $self->{users} ) or ( lc $force eq "force" ) ); + foreach my $key ( keys %{ $self->{users} } ) { + my $status = "enabled"; + $status = "disabled" if ( $self->{users}->{$key}->{status} == 0 ); + main::print_log("[raZberry_lock] User: $key Status: $status"); + } +} + +sub _battery_timer { + my ($self) = @_; + + $self->{battery_timer}->set( $self->{battery_poll_seconds}, + sub { &raZberry_lock::battery_check($self) }, -1 ); +} + +sub _control_user { + my ( $self, $userid, $code, $control ) = @_; + + #curl --globoff "http://rasip:8083/ZWaveAPI/Run/devices[x].UserCode.Set(userid,code,control)" + + my $cmd; + my ( $devid, $instance, $class ) = ( split /-/, $self->{devid} )[ 0, 1, 2 ]; + $cmd = "%5B" + . $devid + . "%5D.UserCode.Set(" + . $userid . "," + . $code . "," + . $control . ")"; + &main::print_log("[raZberry] Enabling usercodes $userid ($devid)...") + if ( $self->{debug} ); + &main::print_log("cmd=$cmd") if ( $self->{debug} > 1 ); + my ( $isSuccessResponse0, $status ) = + &raZberry::_get_JSON_data( $self->{master_object}, 'usercode', $cmd ); + unless ($isSuccessResponse0) { + &main::print_log( + "[raZberry] Error: Problem retrieving data from " . $self->{host} ); + $self->{data}->{retry}++; + return ('0'); + } +} + +sub _update_users { + my ( $self, $device ) = @_; + + #curl --globoff "http://192.168.0.155:8083/ZWaveAPI/Run/devices[7].UserCode.data" + my $cmd; + my ( $devid, $instance, $class ) = ( split /-/, $self->{devid} )[ 0, 1, 2 ]; + $cmd = "%5B" . $devid . "%5D.UserCode.Get()"; + &main::print_log("[raZberry] Getting local usercodes ($devid)...") + if ( $self->{debug} ); + &main::print_log("cmd=$cmd") if ( $self->{debug} > 1 ); + my ( $isSuccessResponse0, $status ) = + &raZberry::_get_JSON_data( $self->{master_object}, 'usercode', $cmd ); + unless ($isSuccessResponse0) { + &main::print_log( + "[raZberry] Error: Problem retrieving data from " . $self->{host} ); + $self->{data}->{retry}++; + return ('0'); + } + $cmd = "%5B" . $devid . "%5D.UserCode.data"; + &main::print_log("[raZberry] Downloading local usercodes from $devid...") + if ( $self->{debug} ); + &main::print_log("cmd=$cmd") if ( $self->{debug} > 1 ); + my ( $isSuccessResponse1, $response ) = + &raZberry::_get_JSON_data( $self->{master_object}, 'usercode_data', + $cmd ); + unless ($isSuccessResponse1) { + &main::print_log( + "[raZberry] Error: Problem retrieving data from " . $self->{host} ); + $self->{data}->{retry}++; + return ('0'); + } + print Dumper $response if ( $self->{debug} > 1 ); + foreach my $key ( keys %{$response} ) { + if ( $key =~ m/^[0-9]*$/ ) { #a number, so a user code + $self->{users}->{"$key"}->{status} = + $response->{"$key"}->{status}->{value}; + } + } + + return ('1'); +} + package raZberry_comm; @raZberry_comm::ISA = ('Generic_Item'); sub new { + my ( $class, $object ) = @_; my $self = {};