From cc4dfe95c96d390a76bc15eebccfe01ccce674af Mon Sep 17 00:00:00 2001 From: nasseeder1 Date: Thu, 5 Dec 2024 20:20:26 +0000 Subject: [PATCH] 76_SolarForecast: possible asynchron mode for Meter & Inverter git-svn-id: https://svn.fhem.de/fhem/trunk@29403 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/76_SolarForecast.pm | 235 ++++++++++++++++++-------- fhem/lib/FHEM/SynoModules/ErrCodes.pm | 18 +- fhem/lib/FHEM/SynoModules/SMUtils.pm | 9 +- 4 files changed, 184 insertions(+), 79 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 0fa0b516e..c46be8164 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 + - feature: 76_SolarForecast: possible asynchron mode for Meter & Inverter - change: 93_DbLog: insertmode 0 - improved output of errors in Logfile - change: 57_Calendar: remove smartmatch replacement as it is now officially in fhem.pl diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index d4151e112..e8ba49423 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -50,6 +50,7 @@ use HttpUtils; eval "use JSON;1;" or my $jsonabs = 'JSON'; ## no critic 'eval' # Debian: sudo apt-get install libjson-perl eval "use AI::DecisionTree;1;" or my $aidtabs = 'AI::DecisionTree'; ## no critic 'eval' +use FHEM::SynoModules::ErrCodes qw(:all); # Error Code Modul use FHEM::SynoModules::SMUtils qw (checkModVer evaljson getClHash @@ -156,6 +157,8 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.39.0" => "04.12.2024 possible asynchron mode for setupMeterDev, setupInverterDevXX ". + "include FHEM::SynoModules::ErrCodes ", "1.38.0" => "30.11.2024 optimize data handling, rename getter solApiData to radiationApiData, ". "set setupStringAzimuth, setupStringDeclination is checked due to dependencies to OpenMeteo ". "attr setupRadiationAPI and setupWeatherDev1 can be set largely independently of each other ". @@ -1339,7 +1342,7 @@ sub Define { notes => \%vNotesIntern, useAPI => 0, useSMUtils => 1, - useErrCodes => 0, + useErrCodes => 1, useCTZ => 1, }; @@ -3769,7 +3772,7 @@ sub __VictronVRM_ApiResponseLogin { return; } elsif ($myjson ne "") { # Evaluiere ob Daten im JSON-Format empfangen wurden - my ($success) = evaljson($hash, $myjson); + my ($success) = evaljson ($hash, $myjson); if (!$success) { $msg = 'ERROR - invalid Victron VRM API response'; @@ -5977,6 +5980,7 @@ sub _attrInverterDev { ## no critic "not used" delete $data{$type}{$name}{inverters}{$in}{ilimit}; delete $data{$type}{$name}{inverters}{$in}{iicon}; delete $data{$type}{$name}{inverters}{$in}{istrings}; + delete $data{$type}{$name}{inverters}{$in}{iasynchron}; delete $data{$type}{$name}{inverters}{$in}{ifeed}; } elsif ($paref->{cmd} eq 'del') { @@ -6338,7 +6342,6 @@ return; ################################################################################### sub Notify { # Es werden nur die Events von Geräten verarbeitet die im Hash $hash->{NOTIFYDEV} gelistet sind (wenn definiert). - # Dadurch kann die Menge der Events verringert werden. In sub DbRep_Define angeben. my $myHash = shift; my $dev_hash = shift; @@ -6350,20 +6353,61 @@ sub Notify { my $events = deviceEvents($dev_hash, 1); return if(!$events); - my $cdref = CurrentVal ($myHash, 'consumerdevs', ''); # alle registrierten Consumer und Schaltdevices + my $debug = getDebug ($myHash); # Debug Mode + + my ($err, $medev, $h, $async); + + ## Meter Event? + ################# + ($err, $medev, $h) = isDeviceValid ( { name => $myName, obj => 'setupMeterDev', method => 'attr' } ); + + if (!$err) { + if ($devName eq $medev) { + $async = $h->{asynchron} // 0; + + if ($debug =~ /notifyHandling/x) { + Log3 ($myName, 1, qq{$myName DEBUG> notifyHandling - Event of Meter device >$devName< received - asynchronous mode: $async}); + } + + if ($async) { + centralTask ($myHash, 1); # keine Events in SolarForecast außer 'state' + return; + } + } + } + + ## Inverter Event? + #################### + for my $in (1..$maxinverter) { + $in = sprintf "%02d", $in; + my $iname = InverterVal ($myHash, $in, 'iname', ''); + + if ($devName eq $iname) { + my $iasync = InverterVal ($myHash, $in, 'iasynchron', 0); + + if ($debug =~ /notifyHandling/x) { + Log3 ($myName, 1, qq{$myName DEBUG> notifyHandling - Event of Inverter device >$devName< received - asynchronous mode: $iasync}); + } + + if ($iasync) { + centralTask ($myHash, 1); # keine Events in SolarForecast außer 'state' + return; + } + } + } + + ## consumer Event? + #################### + my $cdref = CurrentVal ($myHash, 'consumerdevs', ''); # alle registrierten Consumer und Schaltdevices my @consumers = (); @consumers = @{$cdref} if(ref $cdref eq "ARRAY"); - - return if(!@consumers); - - my $debug = getDebug ($myHash); # Debug Mode - - if (grep /^$devName$/, @consumers) { - my ($cname, $cindex); + + if (@consumers && grep /^$devName$/, @consumers) { + my ($cname, $cindex, $dswname); my $type = $myHash->{TYPE}; for my $c (sort{$a<=>$b} keys %{$data{$type}{$myName}{consumers}}) { - my ($err, $cname, $dswname) = getCDnames ($myHash, $c); + ($err, $cname, $dswname) = getCDnames ($myHash, $c); if ($devName eq $cname) { $cindex = $c; @@ -6391,7 +6435,7 @@ sub Notify { return; } - my $async = ConsumerVal ($myHash, $cindex, 'asynchron', 0); + $async = ConsumerVal ($myHash, $cindex, 'asynchron', 0); my $rswstate = ConsumerVal ($myHash, $cindex, 'rswstate', 'state'); if ($debug =~ /notifyHandling/x) { @@ -8785,9 +8829,9 @@ sub _transferInverterValues { next if(!$pvread || !$edread); - my $pvuf = $pvunit =~ /^kW$/xi ? 1000 : 1; - my $pv = ReadingsNum ($indev, $pvread, 0) * $pvuf; # aktuelle Erzeugung (W) - $pv = $pv < 0 ? 0 : sprintf("%.0f", $pv); # Forum: https://forum.fhem.de/index.php/topic,117864.msg1159718.html#msg1159718, https://forum.fhem.de/index.php/topic,117864.msg1166201.html#msg1166201 + my $pvuf = $pvunit =~ /^kW$/xi ? 1000 : 1; + my $pv = ReadingsNum ($indev, $pvread, 0) * $pvuf; # aktuelle Erzeugung (W) + $pv = $pv < 0 ? 0 : sprintf("%.0f", $pv); # Forum: https://forum.fhem.de/index.php/topic,117864.msg1159718.html#msg1159718, https://forum.fhem.de/index.php/topic,117864.msg1166201.html#msg1166201 my $etuf = $etunit =~ /^kWh$/xi ? 1000 : 1; my $etotal = ReadingsNum ($indev, $edread, 0) * $etuf; # Erzeugung total (Wh) @@ -8839,6 +8883,7 @@ sub _transferInverterValues { $data{$type}{$name}{inverters}{$in}{ilimit} = $h->{limit} // 100; # Wirkleistungsbegrenzung $data{$type}{$name}{inverters}{$in}{iicon} = $h->{icon} if($h->{icon}); # Icon des Inverters $data{$type}{$name}{inverters}{$in}{istrings} = $h->{strings} if($h->{strings}); # dem Inverter zugeordnete Strings + $data{$type}{$name}{inverters}{$in}{iasynchron} = $h->{asynchron} if($h->{asynchron}); # Inverter Mode $data{$type}{$name}{inverters}{$in}{ifeed} = $feed; # Eigenschaften der Energielieferung $pvsum += $pv; @@ -17846,6 +17891,7 @@ sub createAssociatedWith { my $medev = AttrVal ($name, 'setupMeterDev', ''); # Meter Device ($ame,$h) = parseParams ($medev); $medev = $ame->[0] // ""; + push @cd, $medev; my $badev = AttrVal ($name, 'setupBatteryDev', ''); # Battery Device ($aba,$h) = parseParams ($badev); @@ -17859,6 +17905,13 @@ sub createAssociatedWith { push @cd, $codev if($codev); push @cd, $dswitch if($dswitch); } + + for my $in (1..$maxinverter) { # Inverter Devices + $in = sprintf "%02d", $in; + my $inc = AttrVal ($name, "setupInverterDev${in}", ""); + my ($ind) = parseParams ($inc); + push @cd, $ind->[0] if($ind->[0]); + } @nd = @cd; @@ -17876,13 +17929,6 @@ sub createAssociatedWith { push @nd, $prd->[0] if($prd->[0]); } - for my $in (1..$maxinverter) { # Inverter Devices - $in = sprintf "%02d", $in; - my $inc = AttrVal ($name, "setupInverterDev${in}", ""); - my ($ind) = parseParams ($inc); - push @nd, $ind->[0] if($ind->[0]); - } - my @ndn = (); for my $e (@nd) { @@ -20868,6 +20914,7 @@ to ensure that the system configuration is correct.

@@ -22023,7 +22084,7 @@ to ensure that the system configuration is correct.
  • setupMeterDev <Meter Device Name> gcon=<Readingname>:<Unit> contotal=<Readingname>:<Unit> gfeedin=<Readingname>:<Unit> feedtotal=<Readingname>:<Unit> - [conprice=<Field>] [feedprice=<Field>]

    + [conprice=<Field>] [feedprice=<Field>] [asynchron=<Option>]

    Defines any device and its readings for measuring energy into or out of the public grid. The module assumes that the numeric value of the readings is positive. @@ -22034,13 +22095,17 @@ to ensure that the system configuration is correct. + + + + @@ -22052,6 +22117,12 @@ to ensure that the system configuration is correct. + + + + + +
    gcon Reading which supplies the power currently drawn from the grid
    contotal Reading which provides the sum of the energy drawn from the grid (a constantly increasing meter)
    If the counter is reset to '0' at the beginning of the day (daily counter), the module handles this situation accordingly.
    In this case, a message is displayed in the log with verbose 3.
    gfeedin Reading which supplies the power currently fed into the grid
    feedtotal Reading which provides the sum of the energy fed into the grid (a constantly increasing meter)
    If the counter is reset to '0' at the beginning of the day (daily counter), the module handles this situation accordingly.
    In this case, a message is displayed in the log with verbose 3.
    Unit the respective unit (W,kW,Wh,kWh)
    conprice Price for the purchase of one kWh (optional). The <field> can be specified in one of the following variants:
    <Remuneration>:<Currency> - Remuneration as a numerical value and its currency
    <Reading>:<Currency> - Reading of the meter device that contains the remuneration : Currency
    <Device>:<Reading>:<Currency> - any device and reading containing the remuneration : Currency
    asynchron Data collection mode according to the ctrlInterval setting (synchronous) or additionally by
    event processing (asynchronous).
    0 - no data collection after receiving an event from the device (default)
    1 - trigger a data collection when an event is received from the device

    @@ -23262,6 +23333,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.

    @@ -24418,7 +24504,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
  • setupMeterDev <Meter Device Name> gcon=<Readingname>:<Einheit> contotal=<Readingname>:<Einheit> gfeedin=<Readingname>:<Einheit> feedtotal=<Readingname>:<Einheit> - [conprice=<Feld>] [feedprice=<Feld>]

    + [conprice=<Feld>] [feedprice=<Feld>] [asynchron=<Option>]

    Legt ein beliebiges Device und seine Readings zur Energiemessung in bzw. aus dem öffentlichen Netz fest. Das Modul geht davon aus, dass der numerische Wert der Readings positiv ist. @@ -24429,13 +24515,17 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. + - + + + - + + @@ -24443,10 +24533,16 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. + + + + + +
    gcon Reading welches die aktuell aus dem Netz bezogene Leistung liefert
    contotal Reading welches die Summe der aus dem Netz bezogenen Energie liefert (ein sich stetig erhöhender Zähler)
    Wird der Zähler zu Beginn des Tages auf '0' zurückgesetzt (Tageszähler), behandelt das Modul diese Situation entsprechend.
    In diesem Fall erfolgt eine Meldung im Log mit verbose 3.
    In diesem Fall erfolgt eine Meldung im Log mit verbose 3.
    gfeedin Reading welches die aktuell in das Netz eingespeiste Leistung liefert
    feedtotal Reading welches die Summe der in das Netz eingespeisten Energie liefert (ein sich stetig erhöhender Zähler)
    Wird der Zähler zu Beginn des Tages auf '0' zurückgesetzt (Tageszähler), behandelt das Modul diese Situation entsprechend.
    In diesem Fall erfolgt eine Meldung im Log mit verbose 3.
    In diesem Fall erfolgt eine Meldung im Log mit verbose 3.
    Einheit die jeweilige Einheit (W,kW,Wh,kWh)
    conprice Preis für den Bezug einer kWh (optional). Die Angabe <Feld> ist in einer der folgenden Varianten möglich:
    <Reading>:<Währung> - Reading des Meter Device das den Preis enthält : Währung
    <Device>:<Reading>:<Währung> - beliebiges Device und Reading welches den Preis enthält : Währung
    feedprice Vergütung für die Einspeisung einer kWh (optional). Die Angabe <Feld> ist in einer der folgenden Varianten möglich:
    <Vergütung>:<Währung> - Vergütung als numerischer Wert und dessen Währung
    <Reading>:<Währung> - Reading des Meter Device das die Vergütung enthält : Währung
    <Device>:<Reading>:<Währung> - beliebiges Device und Reading welches die Vergütung enthält : Währung
    asynchron Modus der Datensammlung entsprechend Einstellung ctrlInterval (synchron) oder zusätzlich durch
    Eventverarbeitung (asynchron).
    0 - keine Datensammlung nach Empfang eines Events des Gerätes (default)
    1 - auslösen einer Datensammlung bei Empfang eines Events des Gerätes

    @@ -24764,6 +24860,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. "HttpUtils": 0, "JSON": 4.020, "FHEM::SynoModules::SMUtils": 1.0270, + "FHEM::SynoModules::ErrCodes": 1.003009, "Time::HiRes": 0, "MIME::Base64": 0, "Math::Trig": 0, diff --git a/fhem/lib/FHEM/SynoModules/ErrCodes.pm b/fhem/lib/FHEM/SynoModules/ErrCodes.pm index 1664f02e8..5e5c299a3 100644 --- a/fhem/lib/FHEM/SynoModules/ErrCodes.pm +++ b/fhem/lib/FHEM/SynoModules/ErrCodes.pm @@ -32,7 +32,7 @@ use warnings; use utf8; use Carp qw(croak carp); -use version; our $VERSION = version->declare('1.3.8'); +use version; our $VERSION = version->declare('1.3.9'); use Exporter ('import'); our @EXPORT_OK = qw(expErrorsAuth expErrors); @@ -203,6 +203,11 @@ my %errsschat = ( # Standa 9001 => "API keys and values not completed", ); +## SolarForecast ## +my %errsolarforecast = ( # Standard Error Codes SolarForecast + 9000 => "malformed JSON string received", +); + ## SSFile ## my %errauthssfile = ( # Authentification Error Codes der File Station API 400 => "invalid user or password", @@ -289,10 +294,11 @@ my %errssfile = ( # Standa ); my %hterr = ( # Hash der TYPE Error Code Spezifikationen - SSCam => {errauth => \%errauthsscam, errh => \%errsscam }, - SSCal => {errauth => \%errauthsscal, errh => \%errsscal }, - SSChatBot => { errh => \%errsschat }, - SSFile => {errauth => \%errauthssfile, errh => \%errssfile }, + SSCam => {errauth => \%errauthsscam, errh => \%errsscam }, + SSCal => {errauth => \%errauthsscal, errh => \%errsscal }, + SSChatBot => { errh => \%errsschat }, + SolarForecast => { errh => \%errsolarforecast }, + SSFile => {errauth => \%errauthssfile, errh => \%errssfile }, ); ############################################################################## @@ -322,7 +328,7 @@ sub expErrors { my $errorcode = shift // carp "got no error code to analyse" && return; my $type = $hash->{TYPE}; - if($hterr{$type} && %{$hterr{$type}{errh}}) { + if ($hterr{$type} && %{$hterr{$type}{errh}}) { my $errh = $hterr{$type}{errh}; # der Error Kodierungshash my $error = $errh->{$errorcode} // $nofound." ".$errorcode; return $error; diff --git a/fhem/lib/FHEM/SynoModules/SMUtils.pm b/fhem/lib/FHEM/SynoModules/SMUtils.pm index 5b0524d4a..38f27dddc 100644 --- a/fhem/lib/FHEM/SynoModules/SMUtils.pm +++ b/fhem/lib/FHEM/SynoModules/SMUtils.pm @@ -26,6 +26,7 @@ ######################################################################################################################### # Version History +# 1.27.4 05.12.2024 expand evaljson for SolarForecast # 1.27.3 26.10.2024 compatibility to SSCam V 9.12.0 # 1.27.2 16.03.2024 change checkModVer text output # 1.27.1 04.12.2023 change checkModVer @@ -60,7 +61,7 @@ use FHEM::SynoModules::ErrCodes qw(:all); # Erro use GPUtils qw( GP_Import GP_Export ); use Carp qw(croak carp); -use version 0.77; our $VERSION = version->declare('1.27.3'); +use version 0.77; our $VERSION = version->declare('1.27.4'); use Exporter ('import'); our @EXPORT_OK = qw( @@ -1185,10 +1186,10 @@ sub evaljson { if ($nojsonmod) { $success = 0; Log3 ($name, 1, "$name - ERROR: Perl module 'JSON' is missing. You need to install it."); - return ($success,$myjson); + return ($success, $myjson); } - eval {decode_json($myjson) + eval {decode_json ($myjson) } or do { if (($hash->{HELPER}{RUNVIEW} && $hash->{HELPER}{RUNVIEW} =~ m/^live_.*hls$/x) || @@ -1203,7 +1204,7 @@ sub evaljson { else { $success = 0; my $errorcode = "9000"; - my $error = expErrors($hash, $errorcode); # Fehlertext zum Errorcode ermitteln + my $error = expErrors ($hash, $errorcode); # Fehlertext zum Errorcode ermitteln if ($error) { setReadingErrorState ($hash, $error, $errorcode);