diff --git a/fhem/CHANGED b/fhem/CHANGED index aec03fa95..86662702b 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - new: 12_HProtocolGateway / 12_HProtocolTank - bugfix: 72_XiaomiDevice: remove unused battery readings for new fans - feature: 49_SSCam: activate/deactivate cam internal PIR-sensor - new: 10_MQTT_GENERIC_BRIDGE an MQTT bridge, which simultaneously diff --git a/fhem/FHEM/12_HProtocolGateway.pm b/fhem/FHEM/12_HProtocolGateway.pm new file mode 100644 index 000000000..923a2e079 --- /dev/null +++ b/fhem/FHEM/12_HProtocolGateway.pm @@ -0,0 +1,430 @@ +# $Id$ +#################################################################################################### +# +# 12_HProtocolGateway.pm +# +# Copyright: Stephan Eisler +# Email: fhem.dev@hausautomatisierung.co +# +# This file is part of fhem. +# +# Fhem is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Fhem is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with fhem. If not, see . +# +#################################################################################################### + +package main; + +use strict; +use warnings; +use DevIo; + +my @tankList = undef; + +my %sets = ( + 'readValues' => 1, +); + +sub HProtocolGateway_Initialize($) { + my ($hash) = @_; + + $hash->{Clients} = "HProtocolTank"; + $hash->{DefFn} = "HProtocolGateway_Define"; + $hash->{InitFn} = "HProtocolGateway_Init"; + $hash->{GetFn} = "HProtocolGateway_Get"; + $hash->{SetFn} = "HProtocolGateway_Set"; + $hash->{AttrFn} = "HProtocolGateway_Attr"; + $hash->{AttrList} = "device " . + "baudrate:300,600,1200,2400,4800,9600 " . + "parityBit:N,E,O " . + "databitsLength:5,6,7,8 " . + "stopBit " . + "pollIntervalMins " . + "mode:Filllevel,Volume,Ullage " . + "path"; +} + +sub HProtocolGateway_Define($$) { + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "Wrong syntax: use define HProtocolGateway " if(int(@a) != 3); + + $hash->{helper}{DeviceName} = $a[2]; + $hash->{Clients} = "HProtocolTank"; + $hash->{STATE} = "Initialized"; + + HProtocolGateway_DeviceConfig($hash); + + HProtocolGateway_Poll($hash) if defined(AttrVal($hash->{NAME}, 'pollIntervalMins', undef)); # if pollIntervalMins defind -> start timer + + return undef; +} + +sub HProtocolGateway_Get($$@) { + my ($hash, $name, $opt, @args) = @_; + + return "\"get $name\" needs at least one argument" unless(defined($opt)); + + if ($opt eq "update") { + HProtocolGateway_GetUpdate($hash); + return "Done."; + } else { + # IMPORTANT! This defines the list of possible commands + my $list = "update:noArg"; + return "Unknown argument $opt, choose one of $list"; + } + + return -1; +} + +sub HProtocolGateway_Set($@) { + my ($hash, @a) = @_; + + my $name = $a[0]; + my $cmd = $a[1]; + + if(!defined($sets{$cmd})) { + return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets) . ":noArg" + } + + if ($cmd eq 'readValues') { + + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'HProtocolGateway_GetUpdate', $hash, 0); + } + return undef +} + +sub HProtocolGateway_GetUpdate($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + foreach (@tankList) { + my $tankHash = $_; + my $command = "\$A"; + if ($attr{$name}{mode} eq "Volume") { + $command = "\$B"; + } elsif ($attr{$name}{mode} eq "Ullage") { + $command = "\$C"; + } + + my $msg = $command . $tankHash->{READINGS}{hID}{VAL} . "\r\n"; + DevIo_SimpleWrite($hash, $msg , 2); + my ($err, $data) = HProtocolGateway_ReadAnswer($hash,$tankHash); + Log3 $name, 5, "err:". $err; + Log3 $name, 5, "data:". $data; + } + + my $pollInterval = AttrVal($hash->{NAME}, 'pollIntervalMins', 0); #restore pollIntervalMins Timer + InternalTimer(gettimeofday() + ($pollInterval * 60), 'HProtocolGateway_Poll', $hash, 0) if ($pollInterval > 0); +} + +sub HProtocolGateway_ReadAnswer($$) { + my ($hash,$tankHash) = @_; + my $name = $hash->{NAME}; + return ("No FD (dummy device?)", undef) + if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD}))); + + for(;;) { + return ("Device lost when reading answer", undef) + if(!$hash->{FD}); + my $rin = ''; + vec($rin, $hash->{FD}, 1) = 1; + my $nfound = select($rin, undef, undef, 3); + if($nfound <= 0) { + next if ($! == EAGAIN() || $! == EINTR()); + my $err = ($! ? $! : "Timeout"); + return("ProtocolGateway_ReadAnswer $err", undef); + } + my $buf = DevIo_SimpleRead($hash); + return ("No data", undef) if(!defined($buf)); + + my $ret = HProtocolGateway_Read($hash, $buf,$tankHash); + return (undef, $ret) if(defined($ret)); + } + +} + +sub HProtocolGateway_Read($@) { + + my ($hash, $data, $tankHash) = @_; + my $name = $hash->{NAME}; + my $buffer = $hash->{PARTIAL}; + + Log3 $name, 5, "HProtocolGateway ($name) - received $data (buffer contains: $buffer)"; + + $buffer .= $data; + + my $msg; + # as long as the buffer contains CR (complete datagramm) + while($buffer =~ m/\r/) + { + ($msg, $buffer) = split("\r", $buffer, 2); + chomp $msg; + HProtocolGateway_ParseMessage($hash, $msg, $tankHash); + } + + $hash->{PARTIAL} = $buffer; + + return $msg if(defined($data)); + return undef; +} + +sub HProtocolGateway_ParseMessage($$) { + my ($hash, $data, $tankHash) = @_; + my $name = $hash->{NAME}; + + $data =~ s/^.//; # remove # + + my ($tankdata,$water,$temperature,$probe_offset,$version,$error,$checksum)=split(/@/,$data); + my $test = "#".$data.$water.$temperature.$probe_offset.$version.$error; + + # calculate XOR CRC + my $check = 0; + $check ^= $_ for unpack 'C*', $test; + # convert to HEX + $check = sprintf '%02X', $check; + + return if($check ne $checksum); + + my ($filllevel,$volume,$ullage) = 0; + + if ($attr{$name}{mode} eq "Filllevel") { + $filllevel = $tankdata; + $volume = HProtocolGateway_Tank($hash,$tankHash,$filllevel); + } elsif ($attr{$name}{mode} eq "Volume") { + $volume = $tankdata; + } elsif ($attr{$name}{mode} eq "Ullage") { + $ullage = $tankdata; + } + + my $sign = substr($temperature,0,1); + $temperature =~ s/^.//; + if ($sign eq "-") { $temperature = int($temperature) * -1 }; + $temperature = $temperature / 10; + + $sign = substr($probe_offset,0,1); + $probe_offset =~ s/^.//; + if ($sign eq "-") { $probe_offset = int($probe_offset) * -1 }; + + my $volume_15C = $volume * (1 + 0.00084 * ( 15 - $temperature )); + $volume_15C = int($volume_15C); + + # Update all received readings + HProtocolGateway_UpdateTankDevice($hash, $tankHash->{NAME}, "ullage", $ullage); + HProtocolGateway_UpdateTankDevice($hash, $tankHash->{NAME}, "filllevel", $filllevel); + HProtocolGateway_UpdateTankDevice($hash, $tankHash->{NAME}, "volume", $volume); + HProtocolGateway_UpdateTankDevice($hash, $tankHash->{NAME}, "volume_15C", $volume_15C); + HProtocolGateway_UpdateTankDevice($hash, $tankHash->{NAME}, "temperature", $temperature); + HProtocolGateway_UpdateTankDevice($hash, $tankHash->{NAME}, "waterlevel", $water); + HProtocolGateway_UpdateTankDevice($hash, $tankHash->{NAME}, "probe_offset", $probe_offset); + HProtocolGateway_UpdateTankDevice($hash, $tankHash->{NAME}, "version", $version); + HProtocolGateway_UpdateTankDevice($hash, $tankHash->{NAME}, "error", $error); +} + +sub HProtocolGateway_UpdateTankDevice($$$$) { + my ($hash, $tankName, $reading, $value) = @_; + + my $message = $tankName . " " . $reading . " " . $value; + + my $success = Dispatch($hash, $message, undef); + + if (!$success) { + my $name = $hash->{NAME}; + Log3 $name, 1, "$name: failed to update tank device"; + } +} + +sub HProtocolGateway_RegisterTank($) { + my ($tankHash) = @_; + + # Remove undefined elements of empty array + @tankList = grep defined, @tankList; + + @tankList = (@tankList, $tankHash); +} + +# called when definition is undefined +# (config reload, shutdown or delete of definition) +sub HProtocolGateway_Undef($$) +{ + my ($hash, $name) = @_; + + # close the connection + DevIo_CloseDev($hash); + + return undef; +} + +# called repeatedly if device disappeared +sub HProtocolGateway_Ready($) +{ + my ($hash) = @_; + + # try to reopen the connection in case the connection is lost + return DevIo_OpenDev($hash, 1, "HProtocolGateway_Init"); +} + +# will be executed upon successful connection establishment (see DevIo_OpenDev()) +sub HProtocolGateway_Init($) +{ + my ($hash) = @_; + + # send a status request to the device + # DevIo_SimpleWrite($hash, "get_status\r\n", 2); + + return undef; +} + +# Called during attribute create/change +sub HProtocolGateway_Attr (@) { + my ($command, $name, $attr, $val) = @_; + my $hash = $defs{$name}; + my $msg = ''; + + if ($attr eq 'poll_interval') { + if (defined($val)) { + if ($val =~ m/^(0*[1-9][0-9]*)$/) { + RemoveInternalTimer($hash); + HProtocolGateway_Poll($hash) if ($main::init_done); + } else { + $msg = 'Wrong poll intervall defined. pollIntervalMins must be a number > 0'; + } + } else { + RemoveInternalTimer($hash); + } + } elsif ($attr eq 'mode') { + $attr{$name}{mode} = $val; + } elsif ($attr eq 'path') { + $attr{$name}{path} = $val; + } elsif ($attr eq 'baudrate') { + $attr{$name}{baudrate} = $val; + HProtocolGateway_DeviceConfig($hash); + } elsif ($attr eq 'databitsLength') { + $attr{$name}{databitsLength} = $val; + HProtocolGateway_DeviceConfig($hash); + } elsif ($attr eq 'parityBit') { + $attr{$name}{parityBit} = $val; + HProtocolGateway_DeviceConfig($hash); + } elsif ($attr eq 'stopBit') { + $attr{$name}{stopBit} = $val; + HProtocolGateway_DeviceConfig($hash); + } + +} + +sub HProtocolGateway_DeviceConfig($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $deviceName = $hash->{helper}{DeviceName}; + $hash->{DeviceName} = $deviceName."@".$attr{$name}{baudrate}.",".$attr{$name}{databitsLength}.",".$attr{$name}{parityBit}.",".$attr{$name}{stopBit}; + DevIo_CloseDev($hash) if(DevIo_IsOpen($hash)); + my $ret = DevIo_OpenDev($hash, 0, "HProtocolGateway_Init"); + return $ret +} + +# Request measurements regularly +sub HProtocolGateway_Poll($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + HProtocolGateway_Set($hash, ($name, 'readValues')); + + my $pollInterval = AttrVal($hash->{NAME}, 'pollIntervalMins', 0); + if ($pollInterval > 0) { + InternalTimer(gettimeofday() + ($pollInterval * 60), 'HProtocolGateway_Poll', $hash, 0); + } +} + +sub HProtocolGateway_Tank($$$) { + my ($hash,$tankHash,$filllevel) = @_; + my $name = $hash->{NAME}; + + my %TankChartHash; + open my $fh, '<', $attr{$name}{path}.'tank'.$tankHash->{READINGS}{hID}{VAL}.'.csv' or die "Cannot open: $!"; + while (my $line = <$fh>) { + $line =~ s/\s*\z//; + my @array = split /,/, $line; + my $key = shift @array; + $TankChartHash{$key} = $array[0]; + } + close $fh; + + my $messwert = $filllevel/10; + my $volume = 0; + + foreach my $level (sort keys %TankChartHash) { + if ($messwert <= $level && $level ne "level") { + $volume = $TankChartHash{$level}; + last; + } + } + retun $volume; +} + +1; + + +=pod +=item summary support for the HLS 6010 Probes +=begin html + + +

HProtocolGateway

+
+ +=end html + +=cut diff --git a/fhem/FHEM/12_HProtocolTank.pm b/fhem/FHEM/12_HProtocolTank.pm new file mode 100644 index 000000000..3f1cce08f --- /dev/null +++ b/fhem/FHEM/12_HProtocolTank.pm @@ -0,0 +1,151 @@ +# $Id$ +#################################################################################################### +# +# 12_HProtocolTank.pm +# +# Copyright: Stephan Eisler +# Email: fhem.dev@hausautomatisierung.co +# +# This file is part of fhem. +# +# Fhem is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Fhem is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with fhem. If not, see . +# +#################################################################################################### + +package main; + +sub HProtocolTank_Initialize($) { + my ($hash) = @_; + + $hash->{DefFn} = "HProtocolTank_Define"; + $hash->{ParseFn} = "HProtocolTank_Parse"; + $hash->{FingerprintFn} = "HProtocolTank_Fingerprint"; + $hash->{Match} = "^[a-zA-Z0-9_]+ [a-zA-Z0-9_]+ [+-]*[0-9]+([.][0-9]+)?"; + $hash->{AttrList} = "event-on-update-reading"; +} + +sub HProtocolTank_Define($$) { + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "Wrong syntax: use define HProtocolTank " if(int(@a) != 3); + + my $name = $a[0]; + my $gateway = $a[2]; + + if (!$hash->{IODev}) { + AssignIoPort($hash, $gateway); + } + + if (defined($hash->{IODev})) { + Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME}; + } else { + Log3 $name, 1, "$name: no I/O device"; + } + + if (defined($hash->{IODev})) { + $iodev = $hash->{IODev}->{NAME}; + } + + $hash->{STATE} = "Initialized"; + + $attr{$name}{room} = "HProtocol"; + + # TODO This has to be updated when renaming the device. + $modules{HProtocolTank}{defptr}{$name} = $hash; + + # TODO A Tank has to be unregistered when it's removed or renamed. + HProtocolGateway_RegisterTank($hash); + + return undef; +} + +sub HProtocolTank_Parse($$) { + my ($iohash, $message) = @_; + + # $message = " " + my @array = split("[ \t][ \t]*", $message); + my $tankName = @array[0]; + my $reading = @array[1]; + my $value = @array[2]; + + my $hash = $modules{HProtocolTank}{defptr}{$tankName}; + + readingsSingleUpdate($hash, $reading, $value, 1); + + return $tankName; +} + +sub HProtocolTank_Fingerprint($$) { + # this subroutine is called before running Parse to check if + # this message is a duplicate message. Refer to FHEM Wiki. +} + +1; + + +=pod +=item summary devices communicating via the HProtocolGateway +=begin html + + +

HProtocolTank

+
    + The HProtocolTank is a fhem module defines a device connected to a HProtocolGateway. + +


    + + + Define +
      + define tank01 HProtocolTank HProtocolGateway
      + setreading tank01 hID 01
      +
      +
      + + Defines an HProtocolTank connected to a HProtocolGateway.

      + +

    + + + Readings +
      +
    • hID
      + 01 - 99 Tank Number / Tank Address
    • +
    • ullage
      + 0..999999 Ullage in litres
    • +
    • filllevel
      + 0..99999 Fill level in cm
    • +
    • volume
      + 0..999999 Volume in litres
    • +
    • volume_15C
      + 0..999999 Volume in litres at 15 °C
    • +
    • temperature
      + -999 - +999 Temperature in °C
    • +
    • waterlevel
      + 0..9999 Water level in mm
    • +
    • probe_offset
      + -9999 - +9999 Probe offset in mm)
    • +
    • version
      + 00..999 Software version
    • +
    • error
      + 0..9 00.. Probe error
    • +

    + + +

+ +=end html + +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index 0081e60e4..50bcde737 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -86,6 +86,8 @@ FHEM/11_OWX_FRM.pm pahenning 1Wire FHEM/11_OWX_CCC.pm pahenning 1Wire FHEM/14_Hideki.pm Sidey/Ralf9 Sonstige Systeme FHEM/12_HMS.pm rudolfkoenig SlowRF +FHEM/12_HProtocolGateway.pm eisler Sonstige Systeme +FHEM/12_HProtocolTank.pm eisler Sonstige Systeme FHEM/13_KS300.pm rudolfkoenig SlowRF FHEM/14_CUL_MAX.pm rudolfkoenig/orphan MAX FHEM/14_CUL_REDIRECT.pm dancer0705/bjoernh Sonstiges