fhem-mirror/FHEM/11_OWX_FRM.pm
phenning e3607a76a9 00_OWX.pm: Neue Version, kleinere Fixes
11_OWX_CCC: Neue Version, kleinere Fixes
11_OWX_FRM: Neue Version, kleinere Fixes
11_OWX_SER: Neue Version, kleinere Fixes
11_OWX_TCP: Neue Version, kleinere Fixes
21_OWAD.pm: Neue Version, Support für OWX_ASYNC entfernt, kleinere Fixes
21_OWCOUNT.pm: Neue Version, Support für OWX_ASYNC entfernt, kleinere Fixes
21_OWID.pm: Neue Version, Support für OWX_ASYNC entfernt, kleinere Fixes
21_OWLCD.pm: Neue Version, Support für OWX_ASYNC entfernt, kleinere Fixes
21_OWMULTI.pm: Neue Version, Support für OWX_ASYNC entfernt, kleinere Fixes
21_OWSWITCH.pm: Neue Version, Support für OWX_ASYNC entfernt, kleinere Fixes
21_OWTHERM.pm: Neue Version, Support für OWX_ASYNC entfernt, kleinere Fixes
21_OWVAR.pm: Neue Version, Support für OWX_ASYNC entfernt, kleinere Fixes

git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@23553 2b470e98-0d58-463d-a4d8-8e2adae1ed80
2021-01-19 13:51:06 +00:00

735 lines
19 KiB
Perl

########################################################################################
#
# OWX_FRM.pm
#
# FHEM module providing hardware dependent functions for the FRM interface of OWX
#
# Prof. Dr. Peter A. Henning
# Contributions by Jens B
#
# $Id$
#
########################################################################################
#
# Provides the following methods for OWX
#
# Define
# Detect
# Alarms
# Complex
# Discover
# Open
# Close
# Reopen
# Read
# Ready
# Verify
# Write
# observer
# device_to_firmata
# firmata_to_device
#
########################################################################################
#
# $hash->{DeviceName} = <firmata-device>:<firmata-pin>
# $hash->{INTERFACE} = "firmata";
# $hash->{HWDEVICE} = <firmata-device>
# $hash->{PIN} = <firmata-pin>
# $hash->{TYPE} = "OWX";
#
########################################################################################
package OWX_FRM;
use strict;
use warnings;
########################################################################################
#
# Constructor
#
########################################################################################
sub new($) {
my ($class,$hash) = @_;
return bless {
hash => $hash,
#-- module version
version => "7.23"
}, $class;
}
########################################################################################
#
# Define - Implements Define method
#
# Parameter def = definition string
#
# Return undef if ok, otherwise error message
#
########################################################################################
sub Define($) {
my ($self,$def) = @_;
my $hash = $self->{hash};
if (!defined($main::modules{FRM})) {
my $ret = "module FRM not yet loaded, please define an FRM device first";
main::Log3 $hash->{NAME},1,"OWX_FRM::Define error: $ret";
$hash->{STATE} = "error: $ret";
return $ret;
}
if (main::FRM_Get_Device_Firmata_Status() != main::FRM_DEVICE_FIRMATA_STATUS_INSTALLED) {
my $ret = "Perl module Device::Firmata not properly installed";
main::Log3 $hash->{NAME},1,"OWX_FRM::Define error: $ret";
$hash->{STATE} = "error: $ret";
}
my @a = split( "[ \t][ \t]*", $def );
my $u = "wrong syntax: define <name> OWX <firmata-device>:<firmata-pin>";
return $u unless int(@a) > 0;
my($fdev,$pin) = split(':',$a[2]);
$self->{pin} = $pin;
$self->{id} = 0;
$self->{name} = $hash->{NAME};
$self->{hash} = $hash;
#-- when the specified device name contains @<digits>, remove these.
#my $dev =~ s/\@\d*//;
#main::AssignIoPort($hash);
#-- store with OWX device
$hash->{DeviceName} = $a[2];
$hash->{INTERFACE} = "firmata";
$hash->{HWDEVICE} = $fdev;
$hash->{PIN} = $pin;
$hash->{ASYNCHRONOUS} = 0;
main::Log3 $hash->{NAME},1,"OWX_FRM::Define warning: version ".$self->{version}." not identical to OWX version ".$main::owx_version
if( $self->{version} ne $main::owx_version);
#-- register IODev InitFn to be called by FRM after connection to Firmata device is initialized
$hash->{IODev} = $main::defs{$hash->{HWDEVICE}};
$main::modules{$main::defs{$hash->{NAME}}{TYPE}}->{InitFn} = "OWX_FRM::Init";
return undef;
}
########################################################################################
#
# Detect - Find out if we have the proper interface
#
# Return 1 if ok, otherwise 0
#
########################################################################################
sub Detect () {
my ($self) = @_;
my $hash = $self->{hash};
my $ret;
my $name = $hash->{NAME};
my $ress = "OWX: 1-Wire bus $name: interface ";
my $iodev = $hash->{IODev};
if (defined $iodev and defined $iodev->{FirmataDevice} and defined $iodev->{FD}) {
$ret=1;
$ress .= "Firmata detected in $iodev->{NAME}";
} else {
$ret=0;
$ress .= defined $iodev ? "$iodev->{NAME} is not connected to Firmata" : "not associated to any FRM device";
}
main::Log(1, $ress);
return $ret;
}
########################################################################################
#
# Alarms - Find devices on the 1-Wire bus, which have the alarm flag set
#
# Return number of alarmed devices
#
########################################################################################
sub Alarms() {
my ($self) = @_;
my $hash = $self->{hash};
#-- get the interface
my $frm = $hash->{IODev};
return 0 unless defined $frm;
my $firmata = $frm->{FirmataDevice};
my $pin = $hash->{PIN};
return 0 unless ( defined $firmata and defined $pin );
$hash->{ALARMDEVS} = undef;
eval {
$firmata->onewire_search_alarms($hash->{PIN});
};
return 0 if ($@);
my $times = main::AttrVal($hash,"ow-read-timeout",1000) / 50; #timeout in ms, defaults to 1 sec
for (my $i=0;$i<$times;$i++) {
if (main::FRM_poll($hash->{IODev})) {
if (defined $hash->{ALARMDEVS}) {
return 1;
}
} else {
select (undef,undef,undef,0.05);
}
}
$hash->{ALARMDEVS} = [];
return 1;
}
########################################################################################
#
# Reopen - Reopen Device
#
########################################################################################
sub Reopen () {
main::Log 1,"[OWX_FRM] Warning: ->Reopen currently not defined";
}
########################################################################################
#
# Init - Initialize the 1-wire device
#
# Parameter hash = hash of bus master
#
# Return 1 : OK
# 0 : not OK
#
########################################################################################
sub Init() {
my ($self) = @_;
if (main::FRM_Get_Device_Firmata_Status() != main::FRM_DEVICE_FIRMATA_STATUS_INSTALLED) {
return 'Error: Perl module Device::Firmata not properly installed';
}
if (defined($self->{OWX})) {
# class method called with parent hash instead of class hash as 1st parameter, fix
$self = $self->{OWX};
}
my $hash = $self->{hash};
my $dev = $hash->{DeviceName};
my $name = $hash->{NAME};
my $pin = $hash->{PIN};
my $msg;
#main::Log 1,"==================> STARTING INIT of 11_OWX_FRM";
select(undef,undef,undef,0.01);
my @args = ($pin);
$hash->{IODev} = $main::defs{$hash->{HWDEVICE}};
#-- 10_FRM.pm is broken
#-- possible workaround
$main::attr{$name}{IODev} = $hash->{IODev}->{NAME};
my $ret = main::FRM_Init_Pin_Client($hash,\@args,Device::Firmata::Constants->PIN_ONEWIRE);
if (defined $ret){
$msg = "Error ".$ret;
main::Log3 $name,1,"OWX_FRM::Init ".$msg;
return $msg;
}
eval {
my $firmata = main::FRM_Client_FirmataDevice($hash);
$hash->{FRM_OWX_CORRELATIONID} = 0;
$firmata->observe_onewire($pin,\&observer,$hash);
$hash->{FRM_OWX_REPLIES} = {};
$hash->{DEVS} = [];
if ( main::AttrVal($hash->{NAME},"buspower","") eq "parasitic" ) {
$firmata->onewire_config($pin,1);
}
};
return main::FRM_Catch($@) if ($@);
$hash->{STATE}="Initialized";
main::InternalTimer(main::gettimeofday()+10, "OWX_Discover", $hash,0);
return undef;
}
########################################################################################
#
# 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 Complex ($$$$) {
my ($self,$owx_dev,$data,$numread) =@_;
my $hash = $self->{hash};
my $name = $hash->{NAME};
my $res = "";
#-- get the interface
my $frm = $hash->{IODev};
return 0 unless defined $frm;
my $firmata = $frm->{FirmataDevice};
my $pin = $hash->{PIN};
return 0 unless ( defined $firmata and defined $pin );
my $ow_command = {};
#-- has match ROM part
if ($owx_dev) {
$ow_command->{"select"} = device_to_firmata($owx_dev);
#-- padding first 9 bytes into result string, since we have this
# in the serial interfaces as well
$res .= "000000000";
}
#-- has data part
if ($data) {
my @data = unpack "C*", $data;
$ow_command->{"write"} = \@data;
$res.=$data;
}
#-- has receive part
if ( $numread > 0 ) {
$ow_command->{"read"} = $numread;
#Firmata sends 0-address on read after skip
$owx_dev = '00.000000000000.00' unless defined $owx_dev;
my $id = $hash->{FRM_OWX_CORRELATIONID};
$ow_command->{"id"} = $hash->{FRM_OWX_CORRELATIONID};
$hash->{FRM_OWX_REQUESTS}->{$id} = {
command => $ow_command,
device => $owx_dev
};
delete $hash->{FRM_OWX_REPLIES}->{$owx_dev};
$hash->{FRM_OWX_CORRELATIONID} = ($id + 1) & 0xFFFF;
}
eval {
$firmata->onewire_command_series( $pin, $ow_command );
};
return 0 if ($@);
my $oldResLength = length($res);
if ($numread) {
my $times = main::AttrVal($hash,"ow-read-timeout",1000) / 50; #timeout in ms, defaults to 1 sec
for (my $i=0;$i<$times;$i++) {
if (main::FRM_poll($hash->{IODev})) {
if (defined $hash->{FRM_OWX_REPLIES}->{$owx_dev}) {
$res .= $hash->{FRM_OWX_REPLIES}->{$owx_dev};
main::OWX_WDBGL($name,5,"OWX_FRM::Complex receiving inside loop no. $i (" . (length($res) - $oldResLength) . " bytes received) ",$res);
return $res;
}
} else {
select (undef,undef,undef,0.05);
}
}
}
main::OWX_WDBGL($name,5,"OWX_FRM::Complex receiving outside loop (" . (length($res) - $oldResLength) . " bytes received) ", $res);
return $res;
}
########################################################################################
#
# Discover - Discover devices on the 1-Wire bus via internal firmware
#
# Parameter hash = hash of bus master
#
# Return 0 : error
# 1 : OK
#
########################################################################################
sub Discover ($) {
my ($self) = @_;
my $hash = $self->{hash};
#main::Log 1,"======================> FRM Discover called";
#-- get the interface
my $frm = $hash->{IODev};
return 0 unless defined $frm;
my $firmata = $frm->{FirmataDevice};
my $pin = $hash->{PIN};
return 0 unless ( defined $firmata and defined $pin );
my $old_devices = $hash->{DEVS};
$hash->{DEVS} = undef;
eval {
my $res = $firmata->onewire_search($hash->{PIN});
#main::Log 1,"=============> result from search is $res, iodev is ".$hash->{IODev};
};
return 0 if ($@);
my $times = main::AttrVal($hash,"ow-read-timeout",1000) / 50; #timeout in ms, defaults to 1 sec
#main::Log 1,"===========> olddevices = $old_devices, tries =$times";
for (my $i=0;$i<$times;$i++) {
if (main::FRM_poll($hash->{IODev})) {
if (defined $hash->{DEVS}) {
return 1;
}
} else {
select (undef,undef,undef,0.05);
}
}
#main::Log 1,"===========> olddevices restored";
$hash->{DEVS} = $old_devices;
return 1;
}
########################################################################################
#
# Open - Open Device
#
########################################################################################
sub Open () {
my ($self) = @_;
my $hash = $self->{hash};
}
########################################################################################
#
# Close - Close Device
#
########################################################################################
sub Close () {
my ($self) = @_;
my $hash = $self->{hash};
}
#######################################################################################
#
# Read - Implement the Read function
#
# Parameter numexp = expected number of bytes
#
#######################################################################################
sub Read(@) {
my ($self,$numexp) = @_;
my $hash = $self->{hash};
my $name = $hash->{NAME};
my $buffer = $hash->{PREBUFFER};
my $owx_dev = $hash->{FRM_OWX_CURRDEV};
my $times = main::AttrVal($hash,"ow-read-timeout",1000) / 50; #timeout in ms, defaults to 1 sec
#-- first read
$buffer .= $hash->{FRM_OWX_REPLIES}->{$owx_dev};
main::OWX_WDBGL($name,5,"OWX_FRM::Read receiving in first read ",$buffer);
return $buffer;
}
########################################################################################
#
# Ready - Implement the Ready function
#
# Return 1 : OK
# 0 : not OK
#
########################################################################################
sub Ready () {
my ($self) = @_;
my $hash = $self->{hash};
my $name = $hash->{NAME};
my $success= 0;
main::Log3 $name,1,"OWX_FRM::Ready function called for bus $name. STATE=".$hash->{STATE};
return $success;
}
########################################################################################
#
# Reset - Reset the 1-Wire bus
#
# Parameter hash = hash of bus master
#
# Return 1 : OK
# 0 : not OK
#
########################################################################################
sub Reset() {
my ($self) = @_;
my $hash = $self->{hash};
#-- get the interface
my $frm = $hash->{IODev};
return undef unless defined $frm;
my $firmata = $frm->{FirmataDevice};
my $pin = $hash->{PIN};
return undef unless ( defined $firmata and defined $pin );
eval {
$firmata->onewire_reset($pin);
};
return 0 if ($@);
return 1;
}
########################################################################################
#
# 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 Verify($) {
my ($self,$dev) = @_;
my $hash = $self->{hash};
foreach my $found ($hash->{DEVS}) {
if ($dev eq $found) {
return 1;
}
}
return 0;
}
#######################################################################################
#
# Write - Implement the write function
#
#
# Parameter cmd = string to be sent
# reset = 1 if initial bus reset has to be done
#
########################################################################################
sub Write(@) {
my ($self,$cmd, $reset) = @_;
my $hash = $self->{hash};
my $name = $hash->{NAME};
my $res = "";
#-- get the interface
my $frm = $hash->{IODev};
unless(defined $frm){
main::Log3 $name,1,"OWX_FRM::Write attempted to undefined device $name";
return 0
}
my $firmata = $frm->{FirmataDevice};
my $pin = $hash->{PIN};
unless ( defined $firmata and defined $pin ){
main::Log3 $name,1,"OWX_FRM::Write attempted to ill-defined device $name";
return 0
}
#-- if necessary, perform a reset operation
$self->Reset()
if( $reset );
main::OWX_WDBGL($name,5,"OWX_FRM::Write Sending out ",$cmd);
my $cmd2 = $cmd;
my $owx_dev ="";
my $ow_command = {};
#-- take away trailing 0xFF
my $takeoff=0;
my $tchar;
for( my $i=length($cmd)-1; $i>=0; $i--){
$tchar = substr($cmd,$i,1);
if( ord($tchar) == 0xff ){
$takeoff++;
}else{
last;
}
}
$cmd2 = substr($cmd,0,length($cmd)-$takeoff);
#-- has match ROM part - need to extract this
$tchar = substr($cmd2,0,1);
if( ord($tchar) == 0x55 ){
#-- ID of the device. Careful, because hash is the hash of busmaster
for(my $i=0;$i<8;$i++){
my $j=int(ord(substr($cmd2,$i+1,1))/16);
my $k=ord(substr($cmd,$i+1,1))%16;
$owx_dev.=sprintf "%1x%1x",$j,$k;
$owx_dev.="."
if($i==0 || $i==6);
}
$owx_dev=uc($owx_dev);
$cmd2 = substr($cmd2,9);
$ow_command->{"select"} = device_to_firmata($owx_dev);
#-- padding first 9 bytes into result string, since we have this
# in the serial interfaces as well
$res .= "000000000";
}
#-- has data part
if ($cmd2) {
my @data = unpack "C*", $cmd2;
$ow_command->{"write"} = \@data;
$res.=$cmd2;
}
#-- pre-content of result buffer
$hash->{PREBUFFER} = $res;
#-- always receive part ??
# if ( $numread > 0 ) {
$ow_command->{"read"} = length($cmd);
#Firmata sends 0-address on read after skip
$owx_dev = '00.000000000000.00' unless defined $owx_dev;
my $id = $hash->{FRM_OWX_CORRELATIONID};
$ow_command->{"id"} = $hash->{FRM_OWX_CORRELATIONID};
$hash->{FRM_OWX_REQUESTS}->{$id} = {
command => $ow_command,
device => $owx_dev
};
delete $hash->{FRM_OWX_REPLIES}->{$owx_dev};
$hash->{FRM_OWX_CORRELATIONID} = ($id + 1) & 0xFFFF;
#}
eval {
$firmata->onewire_command_series( $pin, $ow_command );
};
if ($@) {
main::Log3 $name,1,"OWX_FRM::Write device $name exception " . main::FRM_Catch($@);
return 0
}
}
#######################################################################################
#
# observer function for listening to the FRM device
#
#######################################################################################
sub observer
{
my ( $data,$hash ) = @_;
my $command = $data->{command};
COMMAND_HANDLER: {
$command eq "READ_REPLY" and do {
my $id = $data->{id};
my $request = (defined $id) ? $hash->{FRM_OWX_REQUESTS}->{$id} : undef;
unless (defined $request) {
return unless (defined $data->{device});
my $owx_device = firmata_to_device($data->{device});
my %requests = %{$hash->{FRM_OWX_REQUESTS}};
foreach my $key (keys %requests) {
if ($requests{$key}->{device} eq $owx_device) {
$request = $requests{$key};
$id = $key;
last;
};
};
};
return unless (defined $request);
my $owx_data = pack "C*",@{$data->{data}};
my $owx_device = $request->{device};
$hash->{FRM_OWX_REPLIES}->{$owx_device} = $owx_data;
##
$hash->{FRM_OWX_CURRDEV} = $owx_device;
delete $hash->{FRM_OWX_REQUESTS}->{$id};
return main::OWX_Read($hash);
};
($command eq "SEARCH_REPLY" or $command eq "SEARCH_ALARMS_REPLY") and do {
my @owx_devices = ();
foreach my $device (@{$data->{devices}}) {
push @owx_devices, firmata_to_device($device);
}
if ($command eq "SEARCH_REPLY") {
$hash->{DEVS} = \@owx_devices;
#$main::attr{$hash->{NAME}}{"ow-devices"} = join " ",@owx_devices;
} else {
$hash->{ALARMDEVS} = \@owx_devices;
}
last;
};
}
}
#######################################################################################
#
# translation of strings
#
#######################################################################################
sub device_to_firmata
{
my @device;
foreach my $hbyte ( unpack "A2xA2A2A2A2A2A2xA2", shift ) {
push @device, hex $hbyte;
}
return {
family => shift @device,
crc => pop @device,
identity => \@device,
}
}
sub firmata_to_device
{
my $device = shift;
return sprintf( "%02X.%02X%02X%02X%02X%02X%02X.%02X", $device->{family}, @{ $device->{identity} }, $device->{crc} );
}
1;
=pod
=head1 CHANGES
04.10.2020 jensb
o check for Device::Firmata install error in Define and Init
o GP_Catch() replaced with FRM_Catch() to provide filtered error messages for Perl errors
=cut
=pod
=item device
=item summary to address an OWX interface device via Arduino Firmata
=item summary_DE zur Adressierung eines OWX Interface Device mit Arduino Firmata
=begin html
<a name="OWX_FRM"></a>
<h3>OWX_FRM</h3>
<ul>
See <a href="/fhem/docs/commandref.html#OWX">OWX</a>
</ul>
=end html
=begin html_DE
<a name="OWX_FRM"></a>
<h3>OWX_FRM</h3>
<ul>
<a href="http://fhemwiki.de/wiki/Interfaces_f%C3%BCr_1-Wire">Deutsche Dokumentation im Wiki</a> vorhanden, die englische Version gibt es hier: <a href="/fhem/docs/commandref.html#OWX">OWX</a>
</ul>
=end html_DE
=cut