mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-25 22:09:21 +00:00
git-svn-id: https://svn.fhem.de/fhem/trunk@23392 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
51361e6ae5
commit
40c0ced777
@ -1,11 +1,12 @@
|
||||
# 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.
|
||||
- bugfix: 55_DWD_OpenData: forecast rotation (forum #83097 msg #1108423)
|
||||
- feature: 02_RSS: added readings to show filename and type of background
|
||||
- bugfix: 57_SSCal: accept calendar names which are contain umlauts
|
||||
- feature: 98_weekprofile: add support for MQTT2_DEVICE (txl Beta-User)
|
||||
- feature: 89_FULLY: Added support for sensor information
|
||||
- bugfix: 98_weekprofile: HMCCU-HM send profile without prefix (Forum #1104569)
|
||||
- bugfix: 70_DENON_AVR: serial connection issues
|
||||
- bugfix: 70_DENON_AVR: serial connection issues
|
||||
- feature: 77_SMAEM: attr serialNumber may contain multiple serial numbers
|
||||
- feature: 57_SSCal: handle multiline code in description field if automatic
|
||||
creation of at-devices is used
|
||||
|
@ -35,23 +35,24 @@ Julian date conversion:
|
||||
Copyright (C) 2012 E. G. Richards
|
||||
see Explanatory Supplement to the Astronomical Almanac, 3rd edition, S.E Urban and P.K. Seidelmann eds., chapter 15.11.3, Interconverting Dates and Julian Day Numbers, Algorithm 4
|
||||
|
||||
This script 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.
|
||||
|
||||
The GNU General Public License can be found at
|
||||
|
||||
http://www.gnu.org/copyleft/gpl.html.
|
||||
|
||||
A copy is found in the textfile GPL.txt and important notices to the license
|
||||
from the author is found in LICENSE.txt distributed with these scripts.
|
||||
This script 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.
|
||||
|
||||
This script 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 this script; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
A copy of the GNU General Public License, Version 2 can also be found at
|
||||
|
||||
http://www.gnu.org/licenses/old-licenses/gpl-2.0.
|
||||
|
||||
This copyright notice MUST APPEAR in all copies of the script!
|
||||
|
||||
=cut
|
||||
@ -616,7 +617,7 @@ use constant UPDATE_COMMUNEUNIONS => -2;
|
||||
use constant UPDATE_ALL => -3;
|
||||
|
||||
require Exporter;
|
||||
our $VERSION = '1.014006';
|
||||
our $VERSION = '1.016001';
|
||||
our @ISA = qw(Exporter);
|
||||
our @EXPORT = qw(GetForecast GetAlerts UpdateAlerts UPDATE_DISTRICTS UPDATE_COMMUNEUNIONS UPDATE_ALL);
|
||||
our @EXPORT_OK = qw(IsCommuneUnionWarncellId);
|
||||
@ -850,7 +851,7 @@ sub Shutdown {
|
||||
delete($hash->{".alertsFile".$communeUnion});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (defined($hash->{".forecastBlockingCall"})) {
|
||||
::BlockingKill($hash->{".forecastBlockingCall"});
|
||||
}
|
||||
@ -1399,7 +1400,7 @@ sub RotateForecast {
|
||||
while (defined(::ReadingsVal($name, 'fc'.$daysAvailable.'_date', undef))) {
|
||||
$daysAvailable++;
|
||||
}
|
||||
::Log3 $name, 5, "$name: RotateForecast: $daysAvailable days exist with readings";
|
||||
::Log3 $name, 5, "$name: RotateForecast: START $daysAvailable day(s) exist";
|
||||
|
||||
my $oT = ::ReadingsVal($name, 'fc0_date', undef);
|
||||
my $oldToday = defined($oT)? ParseDateLocal($hash, $oT) : undef;
|
||||
@ -1407,6 +1408,7 @@ sub RotateForecast {
|
||||
my $stationChanged = ::ReadingsVal($name, 'fc_station', '') ne $station;
|
||||
if ($stationChanged) {
|
||||
# different station, delete all existing readings
|
||||
::Log3 $name, 3, "$name: RotateForecast: station has changed, deleting exisiting readings";
|
||||
::CommandDeleteReading(undef, "$name ^fc.*");
|
||||
$daysAvailable = 0;
|
||||
} elsif (defined($oldToday)) {
|
||||
@ -1418,7 +1420,7 @@ sub RotateForecast {
|
||||
}
|
||||
|
||||
my $daysForward = sprintf("%0.0f", ($today - $oldToday)/86400.0); # round() [s] -> [d]
|
||||
::Log3 $name, 5, "$name: RotateForecast: shifting forward by $daysForward day(s) ($oldToday -> $today)";
|
||||
::Log3 $name, 3, "$name: RotateForecast: shifting forward by $daysForward day(s) ($oldToday -> $today)";
|
||||
if ($daysForward > 0) {
|
||||
# different day
|
||||
if ($daysForward < $daysAvailable) {
|
||||
@ -1437,16 +1439,24 @@ sub RotateForecast {
|
||||
push(@shiftProperties, $s.'_time');
|
||||
push(@shiftProperties, $s.'_wwd');
|
||||
}
|
||||
# shift readings forward by days
|
||||
# shift readings forward by days keeping reading timestamps
|
||||
for (my $d=0; $d<($daysAvailable - $daysForward); $d++) {
|
||||
my $sourcePrefix = 'fc'.($daysForward + $d).'_';
|
||||
my $destinationPrefix = 'fc'.$d.'_';
|
||||
foreach my $property (@shiftProperties) {
|
||||
my $value = ::ReadingsVal($name, $sourcePrefix.$property, undef);
|
||||
if (defined($value)) {
|
||||
::readingsBulkUpdate($hash, $destinationPrefix.$property, $value);
|
||||
my $sourceReading = $sourcePrefix.$property;
|
||||
my $destinationReading = $destinationPrefix.$property;
|
||||
my $sourceValue = ::ReadingsVal($name, $sourceReading, undef);
|
||||
if (defined($sourceValue)) {
|
||||
my $timestamp = $hash->{READINGS}{$sourceReading}{TIME};
|
||||
::readingsBulkUpdate($hash, $destinationReading, $sourceValue);
|
||||
$hash->{READINGS}{$destinationReading}{TIME} = $timestamp;
|
||||
} else {
|
||||
::CommandDeleteReading(undef, $destinationPrefix.$property);
|
||||
my $destinationValue = ::ReadingsVal($name, $destinationReading, undef);
|
||||
if (defined($destinationValue)) {
|
||||
::Log3 $name, 3, "$name: RotateForecast WARNING: deleting reading $destinationReading because the source value $sourceReading is undefined";
|
||||
::CommandDeleteReading(undef, "$name $destinationReading");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1457,15 +1467,78 @@ sub RotateForecast {
|
||||
$daysAvailable -= $daysForward;
|
||||
} else {
|
||||
# nothing remains after shifting, delete existing day readings
|
||||
::Log3 $name, 3, "$name: RotateForecast WARNING: deleting all readings because no forecast data remains for rotation";
|
||||
::CommandDeleteReading(undef, "$name ^fc\\d+.*");
|
||||
$daysAvailable = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::Log3 $name, 5, "$name: RotateForecast: END $daysAvailable day(s) remain";
|
||||
|
||||
return $daysAvailable;
|
||||
}
|
||||
|
||||
=head2 PruneForecast($)
|
||||
|
||||
find youngest reading of each day and delete all readings that are older than 1 day
|
||||
excluding the readings "day", "time" and "weekday"
|
||||
|
||||
=over
|
||||
|
||||
=item * param hash: hash of DWD_OpenData device
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub PruneForecast {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if (::AttrVal($name, 'forecastPruning', 0) != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
::Log3 $name, 5, "$name: PruneForecast: START";
|
||||
|
||||
my @readingNames = (grep {/^fc/} keys %{$hash->{READINGS}});
|
||||
|
||||
# find youngest timestamp per day
|
||||
my %youngestTimestamps;
|
||||
foreach my $readingName (@readingNames) {
|
||||
if (!($readingName =~ m/^fc\d*_(day|time|weekday)$/)) {
|
||||
my @parts = $readingName =~ /^fc(\d+)_.*/;
|
||||
if (scalar(@parts) == 1) {
|
||||
my $relativeDay = $parts[0];
|
||||
my $timestamp = ::time_str2num($hash->{READINGS}{TIME});
|
||||
my $youngestTimestamp = $youngestTimestamps{$relativeDay};
|
||||
if (!defined($youngestTimestamp) || $timestamp > $youngestTimestamp) {
|
||||
$youngestTimestamps{$relativeDay} = $timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# delete readings that are too old
|
||||
foreach my $readingName (@readingNames) {
|
||||
if (!($readingName =~ m/^fc\d*_(day|time|weekday)$/)) {
|
||||
my @parts = $readingName =~ /^fc(\d+)_.*/;
|
||||
if (scalar(@parts) == 1) {
|
||||
my $relativeDay = $parts[0];
|
||||
my $timestamp = ::time_str2num($hash->{READINGS}{TIME});
|
||||
my $youngestTimestamp = $youngestTimestamps{$relativeDay};
|
||||
if (defined($youngestTimestamp) && $timestamp < ($youngestTimestamp - 86400)) {
|
||||
::Log3 $name, 3, "$name: PruneForecast WARNING: deleting reading $readingName because it is more than 1 day older than all other readings of the same day";
|
||||
::CommandDeleteReading(undef, "$name $readingName");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::Log3 $name, 5, "$name: PruneForecast: END";
|
||||
}
|
||||
|
||||
sub ProcessForecast;
|
||||
|
||||
=head2 GetForecast($$)
|
||||
@ -1781,10 +1854,10 @@ sub ProcessForecast {
|
||||
my @parts = split(/ at |\n/, $@); # discard anything after " at " or newline
|
||||
if (@parts) {
|
||||
$errorMessage = $parts[0];
|
||||
::Log3 $name, 4, "$name: ProcessForecast error: $parts[0]";
|
||||
::Log3 $name, 4, "$name: ProcessForecast ERROR: $parts[0]";
|
||||
} else {
|
||||
$errorMessage = $@;
|
||||
::Log3 $name, 4, "$name: ProcessForecast error: $@";
|
||||
::Log3 $name, 4, "$name: ProcessForecast ERROR: $@";
|
||||
}
|
||||
} else {
|
||||
# forecast parsed successfully
|
||||
@ -1802,7 +1875,7 @@ sub ProcessForecast {
|
||||
}
|
||||
} else {
|
||||
$errorMessage = 'result file name not defined';
|
||||
::Log3 $name, 3, "$name: ProcessForecast error: temp file name not defined";
|
||||
::Log3 $name, 3, "$name: ProcessForecast ERROR: temp file name not defined";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1843,7 +1916,7 @@ sub GetForecastFinish {
|
||||
# error, skip further processing
|
||||
} elsif (!defined($hash->{".forecastFile"})) {
|
||||
$errorMessage = "internal temp file name missing";
|
||||
::Log3 $name, 3, "$name: GetForecastFinish error: $errorMessage";
|
||||
::Log3 $name, 3, "$name: GetForecastFinish ERROR: $errorMessage";
|
||||
} else {
|
||||
# deserialize forecast
|
||||
my $fh = $hash->{".forecastFileHandle"};
|
||||
@ -1883,7 +1956,7 @@ sub GetForecastFinish {
|
||||
|
||||
::Log3 $name, 5, "$name: GetForecastFinish END";
|
||||
} else {
|
||||
::Log 3, "GetForecastFinish error: device name missing";
|
||||
::Log 3, "GetForecastFinish ERROR: device name missing";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1906,7 +1979,7 @@ sub GetForecastAbort {
|
||||
delete $hash->{".forecastBlockingCall"};
|
||||
delete $hash->{forecastUpdating};
|
||||
$errorMessage = "downloading and processing weather forecast data failed ($errorMessage)";
|
||||
::Log3 $name, 3, "$name: GetForecastAbort error: $errorMessage";
|
||||
::Log3 $name, 3, "$name: GetForecastAbort ERROR: $errorMessage";
|
||||
|
||||
::readingsBeginUpdate($hash);
|
||||
::readingsBulkUpdate($hash, 'state', "forecast error: $errorMessage");
|
||||
@ -1948,7 +2021,7 @@ sub UpdateForecast {
|
||||
|
||||
::readingsBeginUpdate($hash);
|
||||
|
||||
# preprocess existing time readings
|
||||
# preprocess existing readings
|
||||
my $time = time();
|
||||
my ($tSec, $tMin, $tHour, $tMday, $tMon, $tYear, $tWday, $tYday, $tIsdst) = Localtime($hash, $time);
|
||||
my $today = Timelocal($hash, 0, 0, 0, $tMday, $tMon, $tYear);
|
||||
@ -2045,7 +2118,7 @@ sub UpdateForecast {
|
||||
}
|
||||
}
|
||||
|
||||
# delete existing time readings of all days that have not been written
|
||||
# delete readings of all days that have not been updated
|
||||
if ($relativeDay >= 0 && $daysAvailable > $relativeDay + 1) {
|
||||
::Log3 $name, 5, "$name: deleting days with index " . ($relativeDay + 1) . " to " . ($daysAvailable - 1);
|
||||
for (my $d=($relativeDay + 1); $d<$daysAvailable; $d++) {
|
||||
@ -2053,6 +2126,9 @@ sub UpdateForecast {
|
||||
}
|
||||
}
|
||||
|
||||
# delete readings with inconsistent timestamps
|
||||
PruneForecast($hash);
|
||||
|
||||
::readingsBulkUpdate($hash, 'state', 'forecast updated');
|
||||
::readingsEndUpdate($hash, 1);
|
||||
|
||||
@ -2321,10 +2397,10 @@ sub ProcessAlerts {
|
||||
my @parts = split(/ at |\n/, $@); # discard anything after " at " or newline
|
||||
if (@parts) {
|
||||
$errorMessage = $parts[0];
|
||||
::Log3 $name, 4, "$name: ProcessAlerts error: $parts[0]";
|
||||
::Log3 $name, 4, "$name: ProcessAlerts ERROR: $parts[0]";
|
||||
} else {
|
||||
$errorMessage = $@;
|
||||
::Log3 $name, 4, "$name: ProcessAlerts error: $@";
|
||||
::Log3 $name, 4, "$name: ProcessAlerts ERROR: $@";
|
||||
}
|
||||
} else {
|
||||
# alerts parsed successfully
|
||||
@ -2343,7 +2419,7 @@ sub ProcessAlerts {
|
||||
}
|
||||
} else {
|
||||
$errorMessage = 'result file name not defined';
|
||||
::Log3 $name, 3, "$name: ProcessAlerts error: temp file name not defined";
|
||||
::Log3 $name, 3, "$name: ProcessAlerts ERROR: temp file name not defined";
|
||||
}
|
||||
}
|
||||
|
||||
@ -2388,7 +2464,7 @@ sub GetAlertsFinish {
|
||||
# error, skip further processing
|
||||
} elsif (!defined($hash->{".alertsFile".$communeUnion})) {
|
||||
$errorMessage = "internal temp file name missing";
|
||||
::Log3 $name, 3, "$name: GetAlertsFinish error: $errorMessage";
|
||||
::Log3 $name, 3, "$name: GetAlertsFinish ERROR: $errorMessage";
|
||||
} else {
|
||||
# deserialize alerts
|
||||
my $fh = $hash->{".alertsFileHandle".$communeUnion};
|
||||
@ -2458,7 +2534,7 @@ sub GetAlertsFinish {
|
||||
|
||||
::Log3 $name, 5, "$name: GetAlertsFinish END";
|
||||
} else {
|
||||
::Log 3, "GetAlertsFinish error: device name missing";
|
||||
::Log 3, "GetAlertsFinish ERROR: device name missing";
|
||||
}
|
||||
}
|
||||
|
||||
@ -2483,7 +2559,7 @@ sub GetAlertsAbort {
|
||||
delete $hash->{".alertsBlockingCall".$communeUnion};
|
||||
$alertsUpdating[$communeUnion] = undef;
|
||||
$errorMessage = "downloading and processing weather alerts data failed ($errorMessage)";
|
||||
::Log3 $name, 3, "$name: GetAlertsAbort error: $errorMessage";
|
||||
::Log3 $name, 3, "$name: GetAlertsAbort ERROR: $errorMessage";
|
||||
$alertsErrorMessage[$communeUnion] = $errorMessage;
|
||||
|
||||
if ($warncellId >= 0) {
|
||||
@ -2639,7 +2715,7 @@ sub DWD_OpenData_Initialize {
|
||||
$hash->{GetFn} = 'DWD_OpenData::Get';
|
||||
|
||||
$hash->{AttrList} = 'disable:0,1 '
|
||||
.'forecastStation forecastDays forecastProperties forecastResolution:1,3,6 forecastWW2Text:0,1 '
|
||||
.'forecastStation forecastDays forecastProperties forecastResolution:1,3,6 forecastWW2Text:0,1 forecastPruning:0,1 '
|
||||
.'alertArea alertLanguage:DE,EN alertExcludeEvents '
|
||||
.'timezone '
|
||||
.$readingFnAttributes;
|
||||
@ -2653,6 +2729,13 @@ sub DWD_OpenData_Initialize {
|
||||
#
|
||||
# CHANGES
|
||||
#
|
||||
# 03.12.2020 (version 1.16.1) jensb
|
||||
# bugfix: delete destination reading if source reading is undefined when rotationg forecast at daybreak
|
||||
# feature: new attribute forecastPruning to delete forecast readings that are more than 1 day older than the other readings of the same day
|
||||
#
|
||||
# 22.11.2020 (version 1.15.0) jensb
|
||||
# feature: keep reading timestamp when rotating forecast values at daybreak
|
||||
#
|
||||
# 17.06.2020 (version 1.14.6) jensb
|
||||
# bugfix: $warncellId uninitialized when shutdown before first forecast fetch
|
||||
# coding: prototypes removed
|
||||
@ -2871,7 +2954,7 @@ sub DWD_OpenData_Initialize {
|
||||
Note: When value is changed all existing forecast readings will be deleted.
|
||||
</li><br>
|
||||
<li>forecastProperties [<p1>[,<p2>]...], default: Tx, Tn, Tg, TTT, DD, FX1, Neff, RR6c, RRhc, Rh00, ww<br>
|
||||
A list of the properties available can be found <a href="https://opendata.dwd.de/weather/lib/MetElementDefinition.xml">here</a>.<br>
|
||||
See the <a href="https://opendata.dwd.de/weather/lib/MetElementDefinition.xml">DWD forecast property defintions</a> for more details.<br>
|
||||
Notes:<br>
|
||||
- Not all properties are available for all stations and for all hours.<br>
|
||||
- If you remove a property from the list then already existing readings must be deleted manually in continuous mode.<br>
|
||||
@ -2879,6 +2962,12 @@ sub DWD_OpenData_Initialize {
|
||||
<li>forecastWW2Text {0|1}, default: 0<br>
|
||||
Create additional wwd readings containing the weather code as a descriptive text in German language.
|
||||
</li><br>
|
||||
<li>forecastPruning {0|1}, default: 0<br>
|
||||
Search for and delete forecast readings that are more then one day older then other forecast readings of the same day. Pruning will be performed after a successful forecast update.<br>
|
||||
Notes:<br>
|
||||
- Intended to maintain data consistency e.g. when a forecast station changes the reporting hour of a forecast property.<br>
|
||||
- Requires noticable extra computing resources and may cause side effects if your FHEM configuration depends on a reading that is deleted.<br>
|
||||
</li><br>
|
||||
</ul>
|
||||
|
||||
<b>alert</b> related:
|
||||
|
Loading…
x
Reference in New Issue
Block a user