mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-05-04 22:19:38 +00:00

git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@5440 2b470e98-0d58-463d-a4d8-8e2adae1ed80
422 lines
12 KiB
Perl
422 lines
12 KiB
Perl
########################################################################################
|
|
#
|
|
# 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;
|