From 1fa8ce3a5aee84c503ce9d9b9be996819217f71d Mon Sep 17 00:00:00 2001 From: ntruchsess <> Date: Fri, 4 Apr 2014 20:48:28 +0000 Subject: [PATCH] OWX_ASYNC: add OWX_DS2480 busmaster support git-svn-id: https://svn.fhem.de/fhem/trunk@5440 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/11_OWX_DS2480.pm | 421 +++++++++++++++++++++++++++ fhem/FHEM/11_OWX_Executor.pm | 240 ++++++++++++++++ fhem/FHEM/11_OWX_SER.pm | 535 +++++++++++++++++++++++++++++++++++ 3 files changed, 1196 insertions(+) create mode 100644 fhem/FHEM/11_OWX_DS2480.pm create mode 100644 fhem/FHEM/11_OWX_Executor.pm create mode 100644 fhem/FHEM/11_OWX_SER.pm diff --git a/fhem/FHEM/11_OWX_DS2480.pm b/fhem/FHEM/11_OWX_DS2480.pm new file mode 100644 index 000000000..d0203efeb --- /dev/null +++ b/fhem/FHEM/11_OWX_DS2480.pm @@ -0,0 +1,421 @@ +######################################################################################## +# +# OWX_DS2480.pm +# +# FHEM module providing hardware dependent functions for the DS2480 interface of OWX +# +# Prof. Dr. Peter A. Henning +# Norbert Truchsess +# +# $Id: 11_OWX_SER.pm 2013-03 - pahenning $ +# +######################################################################################## +# +# Provides the following methods for OWX +# +# Alarms +# Complex +# Define +# Discover +# Init +# Reset +# Verify +# +######################################################################################## + +package OWX_DS2480; + +use strict; +use warnings; +use Time::HiRes qw( gettimeofday tv_interval usleep ); + +use vars qw/@ISA/; +@ISA='OWX_SER'; + +sub new($) { + my ($class,$serial) = @_; + + return bless $serial,$class; +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a DS2480 bus interface +# +######################################################################################### +# +# Block_2480 - Send data block (Fig. 6 of Maxim AN192) +# +# Parameter hash = hash of bus master, data = string to send +# +# Return response, if OK +# 0 if not OK +# +######################################################################################## + +sub block ($) { + my ($serial,$data) =@_; + my $data2=""; + + my $len = length($data); + + #-- if necessary, prepend E1 character for data mode + if( substr($data,0,1) ne '\xE1') { + $data2 = "\xE1"; + } + #-- all E3 characters have to be duplicated + for(my $i=0;$i<$len;$i++){ + my $newchar = substr($data,$i,1); + $data2=$data2.$newchar; + if( $newchar eq '\xE3'){ + $data2=$data2.$newchar; + } + } + #-- write 1-Wire bus as a single string + $serial->query($data2,$len); +} + +######################################################################################## +# +# query - Write to the 1-Wire bus +# +# Parameter: cmd = string to send to the 1-Wire bus, retlen = expected len of the response +# +# Return: 1 when write was successful. undef otherwise +# +######################################################################################## + +sub query ($$$) { + + my ($serial,$cmd,$retlen) = @_; + my ($i,$j,$k,$l,$m,$n); + + #-- get hardware device + my $hwdevice = $serial->{hwdevice}; + + die "OWX_DS2480: query with no hwdevice" unless (defined $hwdevice); + + $hwdevice->baudrate($serial->{baud}); + $hwdevice->write_settings; + + if( $main::owx_debug > 2){ + main::Log3($serial->{name},3, "OWX_DS2480.query sending out: ".unpack ("H*",$cmd)); + } + + my $count_out = $hwdevice->write($cmd); + + die "OWX_DS2480: Write incomplete ".(defined $count_out ? $count_out : "undefined")." not equal ".(length($cmd))."" if (!(defined $count_out) or ($count_out ne length($cmd))); + + $serial->{retlen} += $retlen; +} + +######################################################################################## +# +# read - read response from the 1-Wire bus +# to be called from OWX ReadFn. +# +# Parameter: - +# +# Return: 1 when at least 1 byte was read. undef otherwise +# +######################################################################################## + +sub read() { + my $serial = shift; + my ($i,$j,$k); + + #-- get hardware device + my $hwdevice = $serial->{hwdevice}; + return undef unless (defined $hwdevice); + + #-- read the data - looping for slow devices suggested by Joachim Herold + my ($count_in, $string_part) = $hwdevice->read(48); + return undef if (not defined $count_in or not defined $string_part); + $serial->{string_in} .= $string_part; + $serial->{retcount} += $count_in; + $serial->{num_reads}++; + if( $main::owx_debug > 2){ + main::Log3($serial->{name},3, "OWX_DS2480 read: Loop no. $serial->{num_reads}"); + } + if( $main::owx_debug > 2){ + main::Log3($serial->{name},3, "OWX_DS2480 read: Receiving in loop no. $serial->{num_reads} ".unpack("H*",$string_part)); + } + return $count_in > 0 ? 1 : undef; +} + +sub response_ready() { + my ($serial) = @_; + return 1 if ($serial->{retcount} >= $serial->{retlen}); + die "OWX_DS2480 read timeout, bytes read: $serial->{retcount}, expected: $serial->{retlen}" if (($serial->{num_reads} > 1) and (tv_interval($serial->{starttime}) > $serial->{timeout})); + return 0; +} + +sub start_query() { + my ($serial) = @_; + #read and discard any outstanding data from previous commands: + while($serial->read()) {}; + + $serial->{string_in} = ""; + $serial->{num_reads} = 0; + $serial->{retlen} = 0; + $serial->{retcount} = 0; + $serial->{starttime} = [gettimeofday]; +} + +######################################################################################## +# +# reset - Reset the 1-Wire bus (Fig. 4 of Maxim AN192) +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub reset() { + + my ($serial) = @_; + my ($res,$r1,$r2); + my $name = $serial->{name}; + $serial->start_query(); + + #-- if necessary, prepend \xE3 character for command mode + #-- Reset command \xC5 + #-- write 1-Wire bus + $serial->query("\xE3\xC5",1); + #-- sleeping for some time (value of 0.07 taken from original OWX_Query_DS2480) + select(undef,undef,undef,0.07); +} + +sub reset_response() { + my ($serial) = @_; + + my $res = ord(substr($serial->{string_in},0,1)); + my $name = $serial->{name}; + + if( !($res & 192) ) { + main::Log3($name,3, "OWX_DS2480 reset failure on bus $name"); + return 0; + } + + if( ($res & 3) == 2 ) { + main::Log3($name,1, "OWX_DS2480 reset Alarm presence detected on bus $name"); + $serial->{ALARMED} = "yes"; + } else { + $serial->{ALARMED} = "no"; + } + $serial->{string_in} = substr($serial->{string_in},1); + return 1; +} + +######################################################################################## +# +# Search_2480 - Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +# search state. +# +# Parameter hash = hash of bus master, mode=alarm,discover or verify +# +# Return 1 : device found, ROM number in owx_ROM_ID and pushed to list (LastDeviceFlag=0) +# or only in owx_ROM_ID (LastDeviceFlag=1) +# 0 : device not found, or ot searched at all +# +######################################################################################## + +sub search ($) { + my ($serial,$mode)=@_; + + my ($sp1,$sp2,$search_direction,$id_bit_number); + + #-- Response search data parsing operates bytewise + $id_bit_number = 1; + + #select(undef,undef,undef,0.5); + + #-- clear 16 byte of search data + @{$serial->{search}}=(0,0,0,0 ,0,0,0,0, 0,0,0,0, 0,0,0,0); + #-- Output search data construction (Fig. 9 of Maxim AN192) + # operates on a 16 byte search response = 64 pairs of two bits + while ( $id_bit_number <= 64) { + #-- address single bits in a 16 byte search string + my $newcpos = int(($id_bit_number-1)/4); + my $newimsk = ($id_bit_number-1)%4; + #-- address single bits in a 8 byte id string + my $newcpos2 = int(($id_bit_number-1)/8); + my $newimsk2 = ($id_bit_number-1)%8; + + if( $id_bit_number <= $serial->{LastDiscrepancy}){ + #-- first use the ROM ID bit to set the search direction + if( $id_bit_number < $serial->{LastDiscrepancy} ) { + $search_direction = ($serial->{ROM_ID}->[$newcpos2]>>$newimsk2) & 1; + #-- at the last discrepancy search into 1 direction anyhow + } else { + $search_direction = 1; + } + #-- fill into search data; + @{$serial->{search}}[$newcpos]+=$search_direction<<(2*$newimsk+1); + } + #--increment number + $id_bit_number++; + } + #-- issue data mode \xE1, the normal search command \xF0 or the alarm search command \xEC + # and the command mode \xE3 / start accelerator \xB5 + if( $mode ne "alarm" ){ + $sp1 = "\xE1\xF0\xE3\xB5"; + } else { + $sp1 = "\xE1\xEC\xE3\xB5"; + } + #-- issue data mode \xE1, device ID, command mode \xE3 / end accelerator \xA5 + $sp2=sprintf("\xE1%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\xE3\xA5",@{$serial->{search}}); + $serial->reset(); + $serial->query($sp1,1); + $serial->query($sp2,16); +} + +sub search_response($) { + my ($serial) = @_; + + return undef unless $serial->reset_response(); + + my $response = substr($serial->{string_in},1); + #-- interpret the return data + if( length($response)!=16 ) { + main::Log3($serial->{name},3, "OWX_DS2480: Search 2nd return has wrong parameter with length = ".(length($response)."")); + return 0; + } + #-- Response search data parsing (Fig. 11 of Maxim AN192) + # operates on a 16 byte search response = 64 pairs of two bits + my $id_bit_number = 1; + #-- clear 8 byte of device id for current search + $serial->{ROM_ID} = [0,0,0,0 ,0,0,0,0]; + + while ( $id_bit_number <= 64) { + #-- adress single bits in a 16 byte string + my $newcpos = int(($id_bit_number-1)/4); + my $newimsk = ($id_bit_number-1)%4; + + #-- retrieve the new ROM_ID bit + my $newchar = substr($response,$newcpos,1); + + #-- these are the new bits + my $newibit = (( ord($newchar) >> (2*$newimsk) ) & 2) / 2; + my $newdbit = ( ord($newchar) >> (2*$newimsk) ) & 1; + + #-- output for test purpose + #print "id_bit_number=$id_bit_number => newcpos=$newcpos, newchar=0x".int(ord($newchar)/16). + # ".".int(ord($newchar)%16)." r$id_bit_number=$newibit d$id_bit_number=$newdbit\n"; + + #-- discrepancy=1 and ROM_ID=0 + if( ($newdbit==1) and ($newibit==0) ){ + $serial->{LastDiscrepancy}=$id_bit_number; + if( $id_bit_number < 9 ){ + $serial->{LastFamilyDiscrepancy}=$id_bit_number; + } + } + #-- fill into device data; one char per 8 bits + $serial->{ROM_ID}->[int(($id_bit_number-1)/8)]+=$newibit<<(($id_bit_number-1)%8); + + #-- increment number + $id_bit_number++; + } + return 1; +} + +######################################################################################## +# +# Level_2480 - Change power level (Fig. 13 of Maxim AN192) +# +# Parameter hash = hash of bus master, newlevel = "normal" or something else +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub Level_2480 ($) { + my ($self,$newlevel) =@_; + my $cmd=""; + my $retlen=0; + #-- if necessary, prepend E3 character for command mode + $cmd = "\xE3"; + + #-- return to normal level + if( $newlevel eq "normal" ){ + $cmd=$cmd."\xF1\xED\xF1"; + $retlen+=3; + #-- write 1-Wire bus + my $res = $self->Query_2480($cmd,$retlen); + return undef if (not defined $res); + #-- process result + my $r1 = ord(substr($res,0,1)) & 236; + my $r2 = ord(substr($res,1,1)) & 236; + if( ($r1 eq 236) && ($r2 eq 236) ){ + main::Log3($self->{name},5, "OWX_SER: Level change to normal OK"); + return 1; + } else { + main::Log3($self->{name},3, "OWX_SER: Failed to change to normal level"); + return 0; + } + #-- start pulse + } else { + $cmd=$cmd."\x3F\xED"; + $retlen+=2; + #-- write 1-Wire bus + my $res = $self->Query_2480($cmd,$retlen); + return undef if (not defined $res); + #-- process result + if( $res eq "\x3E" ){ + main::Log3($self->{name},5, "OWX_SER: Level change OK"); + return 1; + } else { + main::Log3($self->{name},3, "OWX_SER: Failed to change level"); + return 0; + } + } +} + +######################################################################################## +# +# WriteBytePower_2480 - Send byte to bus with power increase (Fig. 16 of Maxim AN192) +# +# Parameter hash = hash of bus master, dbyte = byte to send +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub WriteBytePower_2480 ($) { + + my ($self,$dbyte) =@_; + + my $cmd="\x3F"; + my $ret="\x3E"; + #-- if necessary, prepend \xE3 character for command mode + $cmd = "\xE3".$cmd; + + #-- distribute the bits of data byte over several command bytes + for (my $i=0;$i<8;$i++){ + my $newbit = (ord($dbyte) >> $i) & 1; + my $newchar = 133 | ($newbit << 4); + my $newchar2 = 132 | ($newbit << 4) | ($newbit << 1) | $newbit; + #-- last command byte still different + if( $i == 7){ + $newchar = $newchar | 2; + } + $cmd = $cmd.chr($newchar); + $ret = $ret.chr($newchar2); + } + #-- write 1-Wire bus + my $res = $self->Query($cmd); + #-- process result + if( $res eq $ret ){ + main::Log3($self->{name},5, "OWX_SER::WriteBytePower OK"); + return 1; + } else { + main::Log3($self->{name},3, "OWX_SER::WriteBytePower failure"); + return 0; + } +} + +1; diff --git a/fhem/FHEM/11_OWX_Executor.pm b/fhem/FHEM/11_OWX_Executor.pm new file mode 100644 index 000000000..e77ea15e8 --- /dev/null +++ b/fhem/FHEM/11_OWX_Executor.pm @@ -0,0 +1,240 @@ +package OWX_Executor; + +use strict; +use warnings; + +use constant { + DISCOVER => 1, + ALARMS => 2, + VERIFY => 3, + EXECUTE => 4, + EXIT => 5, + LOG => 6 +}; + +sub new() { + my $class = shift; + my $self = {}; + $self->{worker} = OWX_Worker->new($self); + return bless $self,$class; +}; + +sub discover($) { + my ($self,$hash) = @_; + if($self->{worker}->submit( { command => DISCOVER }, $hash )) { + $self->poll($hash); + return 1; + } + return undef; +} + +sub alarms($) { + my ($self,$hash) = @_; + if($self->{worker}->submit( { command => ALARMS }, $hash )) { + $self->poll($hash); + return 1; + } + return undef; +} + +sub verify($$) { + my ($self,$hash,$device) = @_; + if($self->{worker}->submit( { command => VERIFY, address => $device }, $hash )) { + $self->poll($hash); + return 1; + } + return undef; +} + +sub execute($$$$$$$) { + my ( $self, $hash, $context, $reset, $owx_dev, $data, $numread, $delay ) = @_; + if($self->{worker}->submit( { + command => EXECUTE, + context => $context, + reset => $reset, + address => $owx_dev, + writedata => $data, + numread => $numread, + delay => $delay + }, $hash )) { + $self->poll($hash); + return 1; + } + return undef; +}; + +sub exit($) { + my ( $self,$hash ) = @_; + if($self->{worker}->submit( { command => EXIT }, $hash )) { + $self->poll($hash); + return 1; + } + return undef; +} + +sub poll($) { + my ( $self,$hash ) = @_; + $self->read(); + $self->{worker}->PT_SCHEDULE($hash); + $self->{worker}->scheduleNext($hash); +} + +# start of worker code + +package OWX_Worker; + +use Time::HiRes qw( gettimeofday tv_interval usleep ); +use ProtoThreads; +no warnings 'deprecated'; + +use vars qw/@ISA/; +@ISA='ProtoThreads'; + +sub new($) { + my ($class,$owx) = @_; + + my $worker = PT_THREAD(\&pt_main); + + $worker->{commands} = []; + $worker->{delayed} = {}; + $worker->{owx} = $owx; + + return bless $worker,$class; +} + +sub submit($$) { + my ($self,$command,$hash) = @_; + push @{$self->{commands}}, $command; + $self->PT_SCHEDULE($hash); + return 1; +} + +sub pt_main($) { + my ( $self, $hash ) = @_; + my $item = $self->{item}; + PT_BEGIN($self); + PT_YIELD_UNTIL($item = $self->nextItem($hash)); + $self->{item} = $item; + + REQUEST_HANDLER: { + my $command = $item->{command}; + + $command eq OWX_Executor::DISCOVER and do { + PT_WAIT_THREAD($self->{owx}->{pt_discover},$self->{owx}); + my $devices = $self->{owx}->{pt_discover}->PT_RETVAL(); + if (defined $devices) { + main::OWX_ASYNC_AfterSearch($hash,$devices); + } + PT_EXIT; + }; + + $command eq OWX_Executor::ALARMS and do { + PT_WAIT_THREAD($self->{owx}->{pt_alarms},$self->{owx}); + my $devices = $self->{owx}->{pt_alarms}->PT_RETVAL(); + if (defined $devices) { + main::OWX_ASYNC_AfterAlarms($hash,$devices); + } + PT_EXIT; + }; + + $command eq OWX_Executor::VERIFY and do { + PT_WAIT_THREAD($self->{owx}->{pt_verify},$self->{owx},$item->{address}); + my $devices = $self->{owx}->{pt_verify}->PT_RETVAL(); + if (defined $devices) { + main::OWX_ASYNC_AfterVerify($hash,$devices); + } + PT_EXIT; + }; + + $command eq OWX_Executor::EXECUTE and do { + PT_WAIT_THREAD($self->{owx}->{pt_execute},$self->{owx},$hash,$item->{context},$item->{reset},$item->{address},$item->{writedata},$item->{numread}); + my $res = $self->{owx}->{pt_execute}->PT_RETVAL(); + unless (defined $res) { + main::OWX_ASYNC_AfterExecute($hash,$item->{context},undef,$item->{reset},$item->{address},$item->{writedata},$item->{numread},undef); + PT_EXIT; + } + my $writelen = defined $item->{writedata} ? split (//,$item->{writedata}) : 0; + my @result = split (//, $res); + my $readdata = 9+$writelen < @result ? substr($res,9+$writelen) : ""; + main::OWX_ASYNC_AfterExecute($hash,$item->{context},1,$item->{reset},$item->{address},$item->{writedata},$item->{numread},$readdata); + if (my $delay = $item->{delay}) { + my ($seconds,$micros) = gettimeofday; + my $len = length ($delay); #delay is millis, tv_address works with [sec,micros] + if ($len>3) { + $seconds += substr($delay,0,$len-3); + $micros += (substr ($delay,-3)*1000); + } else { + $micros += ($delay*1000); + } + + if (my $address = $item->{address}) { + my $delayed = $self->{delayed}; + unless ($delayed->{$address}) { + $delayed->{$address} = { items => [] }; + } + $delayed->{$address}->{'until'} = [$seconds,$micros]; + main::Log3 $hash->{NAME},5,"delay after $item->{context} until: $seconds,$micros" + } else { + $self->{execute_delayed} = [$seconds,$micros]; + PT_YIELD_UNTIL(tv_interval($self->{execute_delayed})>=0); + } + } + PT_EXIT; + }; + + $command eq OWX_Executor::EXIT and do { + main::OWX_ASYNC_Disconnected($hash); + PT_EXIT; + }; + main::Log3($hash->{NAME},3,"OWX_Executor: unexpected command: "+$command); + }; + PT_END; +}; + +sub nextItem($) { + my ( $self,$hash ) = @_; + my ($item,$nexttime,$nextaddress); + my $delayed = $self->{delayed}; + foreach my $address (keys %$delayed) { + next if (tv_interval($delayed->{$address}->{'until'}) < 0); + my $delayed_items = $delayed->{$address}->{'items'}; + $item = shift @$delayed_items; + delete $delayed->{$address} unless @$delayed_items; + last; + }; + unless ($item) { + $item = shift @{$self->{commands}}; + if ($item and my $address = $item->{address}) { + if ($delayed->{$address}) { + push @{$delayed->{$address}->{'items'}},$item; + return undef; + }; + }; + }; + if ($item) { + if($item->{context}) { + main::Log3 $hash->{NAME},5,"OWX_Executor: item $item->{context} for $item->{address} eligible to run"; + } else { + main::Log3 $hash->{NAME},5,"OWX_Executor: command $item->{command} eligible to run"; + } + } + return $item; +} + +sub scheduleNext($) { + my ($self,$hash) = @_; + my $delayed = $self->{delayed}; + my $nexttime; + foreach my $address (keys %$delayed) { + if (my $until = $delayed->{$address}->{'until'}) { + $nexttime = $until unless ($nexttime and tv_interval($nexttime,$until) < 0); + } + }; + if ($nexttime) { + main::RemoveInternalTimer($hash); + main::Log3 $hash->{NAME},5,"schedule next item at $nexttime->[0].$nexttime->[1] ".tv_interval($nexttime); + main::InternalTimer( "$nexttime->[0].$nexttime->[1]", "OWX_ASYNC_Poll", $hash, 0 ); + } +} + +1; diff --git a/fhem/FHEM/11_OWX_SER.pm b/fhem/FHEM/11_OWX_SER.pm new file mode 100644 index 000000000..55ff79f2b --- /dev/null +++ b/fhem/FHEM/11_OWX_SER.pm @@ -0,0 +1,535 @@ +######################################################################################## +# +# OWX_SER.pm +# +# FHEM module providing hardware dependent functions for the serial (USB) interface of OWX +# +# Prof. Dr. Peter A. Henning +# Norbert Truchsess +# +# $Id: 11_OWX_SER.pm 2013-03 - pahenning $ +# +######################################################################################## +# +# Provides the following methods for OWX +# +# Alarms +# Complex +# Define +# Discover +# Init +# Reset +# Verify +# +######################################################################################## + +package OWX_SER; + +use strict; +use warnings; + +use vars qw/@ISA/; +@ISA='OWX_Executor'; + +use ProtoThreads; +no warnings 'deprecated'; + +######################################################################################## +# +# Constructor +# +######################################################################################## + +sub new() { + my $class = shift; + + require "$main::attr{global}{modpath}/FHEM/11_OWX_Executor.pm"; + + my $self = OWX_Executor->new(); + + $self->{interface} = "serial"; + #-- baud rate serial interface + $self->{baud} = 9600; + #-- 16 byte search string + $self->{search} = [0,0,0,0 ,0,0,0,0, 0,0,0,0, 0,0,0,0]; + $self->{ROM_ID} = [0,0,0,0 ,0,0,0,0]; + #-- search state for 1-Wire bus search + $self->{LastDiscrepancy} = 0; + $self->{LastFamilyDiscrepancy} = 0; + $self->{LastDeviceFlag} = 0; + #-- module version + $self->{version} = 4.0; + $self->{alarmdevs} = []; + $self->{devs} = []; + $self->{pt_alarms} = PT_THREAD(\&pt_alarms); + $self->{pt_discover} = PT_THREAD(\&pt_discover); + $self->{pt_verify} = PT_THREAD(\&pt_verify); + $self->{pt_execute} = PT_THREAD(\&pt_execute); + + $self->{timeout} = [1,0]; #default timeout 1 sec. + + return bless $self,$class; +} + +######################################################################################## +# +# Public methods +# +######################################################################################## +# +# Define - Implements Define method +# +# Parameter def = definition string +# +# Return undef if ok, otherwise error message +# +######################################################################################## + +sub Define ($$) { + my ($self,$hash,$def) = @_; + my @a = split("[ \t][ \t]*", $def); + + $self->{name} = $hash->{NAME}; + + #-- check syntax + if(int(@a) < 3){ + return "OWX_SER: Syntax error - must be define OWX " + } + my $dev = $a[2]; + + #-- when the specified device name contains @ already, use it as supplied + if ( $dev !~ m/\@\d*/ ){ + $hash->{DeviceName} = $dev."\@9600"; + } + $dev = split('@',$dev); + #-- let fhem.pl MAIN call OWX_Ready when setup is done. + $main::readyfnlist{"$hash->{NAME}.$dev"} = $hash; + + return undef; +} + +######################################################################################## +# +# Alarms - Find devices on the 1-Wire bus, which have the alarm flag set +# +# Return number of alarmed devices +# +######################################################################################## + +sub pt_alarms () { + my ($thread,$self) = @_; + + PT_BEGIN($thread); + $self->{alarmdevs} = []; + #-- Discover all alarmed devices on the 1-Wire bus + $self->first("alarm"); + do { + $self->next("alarm"); + PT_WAIT_UNTIL($self->response_ready()); + PT_EXIT unless $self->next_response("alarm"); + } while( $self->{LastDeviceFlag}==0 ); + main::Log3($self->{name},1, " Alarms = ".join(' ',@{$self->{alarmdevs}})); + PT_EXIT($self->{alarmdevs}); + PT_END; +} + +######################################################################################## +# +# Complex - Send match ROM, data block and receive bytes as response +# +# Parameter hash = hash of bus master, +# owx_dev = ROM ID of device +# data = string to send +# numread = number of bytes to receive +# +# Return response, if OK +# 0 if not OK +# +######################################################################################## + +sub pt_execute($$$$$$$) { + my ($thread, $self, $hash, $context, $reset, $address, $writedata, $numread) = @_; + + PT_BEGIN($thread); + + #-- get the interface + my $interface = $self->{interface}; + my $hwdevice = $self->{hwdevice}; + + PT_EXIT unless (defined $hwdevice); + + $self->reset() if ($reset); + + my $dev = $address; + my $data = $writedata; + + my $select; + my $res2 = ""; + my ($i,$j,$k); + + #-- has match ROM part + if( $dev ){# command => EXECUTE, +# context => $context, +# reset => $reset, +# address => $owx_dev, +# writedata => $data, +# numread => $numread, +# delay => $delay + + + #-- ID of the device + my $owx_rnf = substr($dev,3,12); + my $owx_f = substr($dev,0,2); + + #-- 8 byte 1-Wire device address + my @rom_id =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + $dev=~s/\.//g; + for(my $i=0;$i<8;$i++){ + $rom_id[$i]=hex(substr($dev,2*$i,2)); + } + $select=sprintf("\x55%c%c%c%c%c%c%c%c",@rom_id).$data; + #-- has no match ROM part + } else { + $select=$data; + } + #-- has receive data part + if( $numread >0 ){ + #$numread += length($data); + for( my $i=0;$i<$numread;$i++){ + $select .= "\xFF"; + }; + } + + #-- for debugging + if( $main::owx_debug > 1){ + main::Log3($self->{name},3,"OWX_SER::Execute: Sending out ".unpack ("H*",$select)); + } + $self->block($select); + + PT_WAIT_UNTIL($self->response_ready()); + + PT_EXIT if ($reset and !$self->reset_response()); + + my $res = $self->{string_in}; + #-- for debugging + if( $main::owx_debug > 1){ + main::Log3($self->{name},3,"OWX_SER::Execute: Receiving ".unpack ("H*",$res)); + } + + PT_EXIT($res); + PT_END; +} + +######################################################################################## +# +# Discover - Find devices on the 1-Wire bus +# +# Parameter hash = hash of bus master +# +# Return 1, if alarmed devices found, 0 otherwise. +# +######################################################################################## + +sub pt_discover($) { + my ($thread,$self) = @_; + PT_BEGIN($thread); + #-- Discover all alarmed devices on the 1-Wire bus + $self->first("discover"); + do { + $self->next("discover"); + PT_WAIT_UNTIL($self->response_ready()); + PT_EXIT unless $self->next_response("discover"); + } while( $self->{LastDeviceFlag}==0 ); + PT_EXIT($self->{devs}); + PT_END; +} + +######################################################################################## +# +# Init - Initialize the 1-wire device +# +# Parameter hash = hash of bus master +# +# Return 1 or Errormessage : not OK +# 0 or undef : OK +# +######################################################################################## + +sub initialize($) { + my ($self,$hash) = @_; + my ($i,$j,$k,$l,$res,$ret,$ress); + #-- Second step in case of serial device: open the serial device to test it + my $msg = "OWX_SER: Serial device $hash->{DeviceName}"; + main::DevIo_OpenDev($hash,0,undef); + my $hwdevice = $hash->{USBDev}; + if(!defined($hwdevice)){ + die $msg." not defined: $!"; + } else { + main::Log3($hash->{NAME},1,$msg." defined"); + } + $hwdevice->reset_error(); + $hwdevice->baudrate(9600); + $hwdevice->databits(8); + $hwdevice->parity('none'); + $hwdevice->stopbits(1); + $hwdevice->handshake('none'); + $hwdevice->write_settings; + #-- store with OWX device + $self->{hwdevice} = $hwdevice; + + #-- Third step detect busmaster on serial interface + + my $name = $self->{name}; + my $ress0 = "OWX_SER::Detect 1-Wire bus $name: interface "; + $ress = $ress0; + + my $interface; + + require "$main::attr{global}{modpath}/FHEM/11_OWX_DS2480.pm"; + my $ds2480 = OWX_DS2480->new($self); + + #-- timing byte for DS2480 + $ds2480->start_query(); + $ds2480->query("\xC1",1); + do { + $ds2480->read(); + } while (!$ds2480->response_ready()); + + #-- Max 4 tries to detect an interface + for($l=0;$l<100;$l++) { + #-- write 1-Wire bus (Fig. 2 of Maxim AN192) + $ds2480->start_query(); + $ds2480->query("\x17\x45\x5B\x0F\x91",5); + do { + $ds2480->read(); + } while (!$ds2480->response_ready()); + $res = $ds2480->{string_in}; + #-- process 4/5-byte string for detection + if( !defined($res)){ + $res=""; + $ret=1; + }elsif( ($res eq "\x16\x44\x5A\x00\x90") || ($res eq "\x16\x44\x5A\x00\x93")){ + $ress .= "master DS2480 detected for the first time"; + $interface="DS2480"; + $ret=0; + } elsif( $res eq "\x17\x45\x5B\x0F\x91"){ + $ress .= "master DS2480 re-detected"; + $interface="DS2480"; + $ret=0; + } elsif( ($res eq "\x17\x0A\x5B\x0F\x02") || ($res eq "\x00\x17\x0A\x5B\x0F\x02") || ($res eq "\x30\xf8\x00") || ($res eq "\x06\x00\x09\x07\x80")){ + $ress .= "passive DS9097 detected"; + $interface="DS9097"; + $ret=0; + } else { + $ret=1; + } + last + if( $ret==0 ); + $ress .= "not found, answer was "; + for($i=0;$i{NAME},1, $ress); + $ress = $ress0; + #-- sleeping for some time + select(undef,undef,undef,0.5); + } + if( $ret == 1 ){ + $interface=undef; + $ress .= "not detected, answer was "; + for($i=0;$i{interface} = $interface; + if ($interface eq "DS2480") { + return $ds2480; + } elsif ($interface eq "DS9097") { + require "$main::attr{global}{modpath}/FHEM/11_OWX_DS9097.pm"; + return OWX_DS9097->new($self); + } else { + die $ress; + } +} + +sub Disconnect($) { + my ($self,$hash) = @_; + main::DevIo_Disconnected($hash); + delete $self->{hwdevice}; + $self->{interface} = "serial"; +} + +######################################################################################## +# +# Verify - Verify a particular device on the 1-Wire bus +# +# Parameter hash = hash of bus master, dev = 8 Byte ROM ID of device to be tested +# +# Return 1 : device found +# 0 : device not +# +######################################################################################## + +sub pt_verify ($) { + my ($thread,$self,$dev) = @_; + my $i; + PT_BEGIN($thread); + #-- from search string to byte id + my $devs=$dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + @{$self->{ROM_ID}}[$i]=hex(substr($devs,2*$i,2)); + } + #-- reset the search state + $self->{LastDiscrepancy} = 64; + $self->{LastDeviceFlag} = 0; + + $self->reset(); + #-- now do the search + $self->next("verify"); + PT_WAIT_UNTIL($self->response_ready()); + PT_EXIT unless $self->next_response("verify"); + + my $dev2=sprintf("%02X.%02X%02X%02X%02X%02X%02X.%02X",@{$self->{ROM_ID}}); + #-- reset the search state + $self->{LastDiscrepancy} = 0; + $self->{LastDeviceFlag} = 0; + #-- check result + if ($dev eq $dev2){ + PT_EXIT(1); + }else{ + PT_EXIT; + } + PT_END; +} + +####################################################################################### +# +# Private methods +# +####################################################################################### +# +# First - Find the 'first' devices on the 1-Wire bus +# +# Parameter hash = hash of bus master, mode +# +# Return 1 : device found, ROM number pushed to list +# 0 : no device present +# +######################################################################################## + +sub first($) { + my ($self) = @_; + + #-- clear 16 byte of search data + @{$self->{search}} = (0,0,0,0 ,0,0,0,0, 0,0,0,0, 0,0,0,0); + #-- reset the search state + $self->{LastDiscrepancy} = 0; + $self->{LastDeviceFlag} = 0; + $self->{LastFamilyDiscrepancy} = 0; +} + +####################################################################################### +# +# Search - Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +# search state. +# +# Parameter hash = hash of bus master, mode=alarm,discover or verify +# +# Return 1 : device found, ROM number in owx_ROM_ID and pushed to list (LastDeviceFlag=0) +# or only in owx_ROM_ID (LastDeviceFlag=1) +# 0 : device not found, or ot searched at all +# +######################################################################################## + +sub next($) { + my ($self,$mode)=@_; + + #-- if the last call was the last one, no search + return undef if ( $self->{LastDeviceFlag} == 1 ); + + #-- now do the search + $self->search($mode); +} + +sub next_response($) { + my ($self,$mode) = @_; + + #TODO find out where contents of @owx_fams come from: + my @owx_fams=(); + + return undef unless $self->search_response(); + + #-- character version of device ROM_ID, first byte = family + my $dev=sprintf("%02X.%02X%02X%02X%02X%02X%02X.%02X",@{$self->{ROM_ID}}); + + #--check if we really found a device + if( main::OWX_CRC($self->{ROM_ID})!= 0){ + #-- reset the search + main::Log3($self->{name},1, "OWX_SER::Search CRC failed : $dev"); + $self->{LastDiscrepancy} = 0; + $self->{LastDeviceFlag} = 0; + $self->{LastFamilyDiscrepancy} = 0; + die "OWX_SER::Search CRC failed : $dev"; + } + + #-- for some reason this does not work - replaced by another test, see below + #if( $self->{LastDiscrepancy}==0 ){ + # $self->{LastDeviceFlag}=1; + #} + #-- + if( $self->{LastDiscrepancy}==$self->{LastFamilyDiscrepancy} ){ + $self->{LastFamilyDiscrepancy}=0; + } + + #-- mode was to verify presence of a device + if ($mode eq "verify") { + main::Log3($self->{name},5, "OWX_SER::Search: device verified $dev"); + #-- mode was to discover devices + } elsif( $mode eq "discover" ) { + #-- check families + my $famfnd=0; + foreach (@owx_fams){ + if( substr($dev,0,2) eq $_ ){ + #-- if present, set the fam found flag + $famfnd=1; + last; + } + } + push(@owx_fams,substr($dev,0,2)) if( !$famfnd ); + foreach (@{$self->{devs}}){ + if( $dev eq $_ ){ + #-- if present, set the last device found flag + $self->{LastDeviceFlag}=1; + last; + } + } + if( $self->{LastDeviceFlag}!=1 ){ + #-- push to list + push(@{$self->{devs}},$dev); + main::Log3($self->{name},5, "OWX_SER::Search: new device found $dev"); + } + #-- mode was to discover alarm devices + } else { + for(my $i=0;$i<@{$self->{alarmdevs}};$i++){ + if( $dev eq ${$self->{alarmdevs}}[$i] ){ + #-- if present, set the last device found flag + $self->{LastDeviceFlag}=1; + last; + } + } + if( $self->{LastDeviceFlag}!=1 ){ + #--push to list + push(@{$self->{alarmdevs}},$dev); + main::Log3($self->{name},5, "OWX_SER::Search: new alarm device found $dev"); + } + } + return 1; +} + +1;