mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-02-02 01:40:34 +00:00
c811015142
git-svn-id: https://svn.fhem.de/fhem/trunk@22510 2b470e98-0d58-463d-a4d8-8e2adae1ed80
1548 lines
54 KiB
Perl
1548 lines
54 KiB
Perl
###############################################################################
|
|
#
|
|
# $Id$
|
|
#
|
|
# By (c) 2019 FHEM user 'pizmus' (pizmus at web de)
|
|
#
|
|
# Based on 70_SolarEdgeAPI.pm from https://github.com/felixmartens/fhem by
|
|
# (c) 2018 Felix Martens (felix at martensmail dot de)
|
|
#
|
|
# Based on 46_TeslaPowerwall2AC by
|
|
# (c) 2017 Copyright: Marko Oldenburg (leongaultier at gmail dot com)
|
|
#
|
|
# All rights reserved
|
|
#
|
|
# 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
|
|
# 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 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.
|
|
#
|
|
###############################################################################
|
|
|
|
package main;
|
|
|
|
use strict;
|
|
use warnings;
|
|
use HttpUtils;
|
|
|
|
###############################################################################
|
|
#
|
|
# Note: Always call the JSON module via "eval":
|
|
#
|
|
# $data = eval{decode_json($data)};
|
|
# if($@){
|
|
# Log3($SELF, 2, "$TYPE ($SELF) - error while request: $@");
|
|
# readingsSingleUpdate($hash, "state", "error", 1);
|
|
# return;
|
|
# }
|
|
#
|
|
###############################################################################
|
|
|
|
my $solarEdgeAPI_missingModul = "";
|
|
eval "use JSON;1" or $solarEdgeAPI_missingModul .= "JSON ";
|
|
|
|
###############################################################################
|
|
#
|
|
# versioning scheme: <majorVersion>.<minorVersion>.<patchVersion>[betaXYZ]
|
|
#
|
|
# The <majorVersion> is incremented for changes which are not backward compatible.
|
|
# A change of the <majorVersion> may require adaptations on the user side, for
|
|
# some or all users, e.g. because a reading is removed or has a new meaning.
|
|
#
|
|
# The <minorVersion> is incremented for changes which are backward compatible,
|
|
# e.g. added functionality which does not impact old functionality.
|
|
#
|
|
# The <patchVersion> is incremented for small bug fixes, changes of source code
|
|
# comments or documentation.
|
|
#
|
|
# A string starting with "beta" is attached for release candidates which are
|
|
# distributed for testing. If no issues are found in a beta version, the "beta"
|
|
# string is removed and the source file is submitted.
|
|
#
|
|
###############################################################################
|
|
#
|
|
# 1.0.0 initial version as copied from https://github.com/felixmartens/fhem
|
|
# with minimal changes to be able to submit it to FHEM SVN
|
|
#
|
|
# 1.1.0beta Detect that site does not support the "currentPowerFlow" API.
|
|
# Read "overview" API to get the current power.
|
|
# Added attributes enableStatusReadings, enableAggregatesReadings,
|
|
# and enableOverviewReadings.
|
|
# Note: This version was released by accident with "beta" in the
|
|
# version string.
|
|
#
|
|
# 1.1.1 source code formatting
|
|
# added TODOs in the source code
|
|
#
|
|
# 1.2.0 added internals that count requests, successful responses and error
|
|
# responses
|
|
# added "set restartTimer" and "set resetDebugCounters"
|
|
# added attributes:
|
|
# intervalAtNightTime
|
|
# dayTimeStartHour
|
|
# nightTimeStartHour
|
|
# enableDebugReadings
|
|
# added internal NUMBER_OF_REQUESTS_PER_DAY that shows the
|
|
# theoretical number of http requests per day, based on current
|
|
# attribute settings
|
|
# Parameter "interval" of the "define" function is now optional. If
|
|
# it is not provided the default value "auto" is used.
|
|
# If the new attributes are not set by the user, the default values
|
|
# are chosen so that behavior is same as in previous versions.
|
|
# Restart periodic timer during _Define instead of _Notify.
|
|
#
|
|
# 1.3.0 show SolarEdge logo to comply with requirement from API documentation
|
|
#
|
|
# 1.4.0 new reading groups: dailyAggregates, storage, dailyStorage,
|
|
# dailyDetails, dailyOverview
|
|
#
|
|
# 2.0.0 changes which are not backward compatible:
|
|
# - "define" does not assign attribute room="Photovoltaik" anymore
|
|
# reason: different users organize rooms differently, it is not the business of the FHEM module
|
|
# impact: attribute room has to be assigned by user for new devices
|
|
# - remove parameter "interval" of "define" function
|
|
# reason: The interval and other related settings are configured via attributes.
|
|
# Attributes are easy to change while the device is alive. Making the same setting
|
|
# via define is redundant and increases complexity.
|
|
# impact: existing devices will fail after update/restart if the optional parameter
|
|
# was used. The device definition has to be changed by removing the last parameter.
|
|
# - rename attribute "interval" to "intervalAtDayTime"
|
|
# reason: make names consistent (intervalAtDayTime/intervalAtNightTime)
|
|
# impact: All users who have specified attribute "interval" must change it to "intervalAtDayTime".
|
|
# - default values of attributes:
|
|
# "enableStatusReadings" -> change from 1 to 0
|
|
# "enableAggregatesReadings" -> change from 1 to 0
|
|
# "enableOverviewReadings" -> change from 0 to 1
|
|
# "enableDailyDetailsReadings" -> change from 0 to 1
|
|
# "enableDailyOverviewReadings" -> change from 0 to 1
|
|
# "dayTimeStartHour" -> change from 7 to 6
|
|
# "intervalAtDayTime" -> change from 300 to 215
|
|
# reason: provide a simple default configuration that works as a good starting point for new users
|
|
# impact: Users that have started with older versions, and who rely on default values, have to set attributes.
|
|
# - do not show number of queue entries in readings "state" and "actionQueue".
|
|
# example of state value (old behavior): "fetch data - 2 entries in the Queue"
|
|
# reason: not a good value of "state" to trigger on
|
|
# impact: Most likely none.
|
|
# - Do not show http errors in readings "state" and "lastRequestError", write error message to log file instead.
|
|
# reason: not a good value of "state" to trigger on. Information should be in the log file.
|
|
# impact: Most likely none. From now on look at log file for error messages.
|
|
# - Do not show JSON errors in readings "JSON Error" and "state", write error message to log file instead.
|
|
# reason: not a good value of "state" to trigger on. Information should be in the log file.
|
|
# impact: Most likely none. From now on look at log file for error messages.
|
|
# - Do not report "aggregates response is not a Hash" via reading "error".
|
|
# Do not report "API currentPowerFlow is not supported by site." via reading "error".
|
|
# reason: Information should be in the log file.
|
|
# impact: Most likely none.
|
|
# - Do not assign text messages to *_status readings of "status" readings group.
|
|
# Use "-" instead if no data is available.
|
|
# reason: Simplify automatic processing of readings.
|
|
# impact: User needs to change e.g. "notify" definitions, if any.
|
|
# - Set the internal "STATE" and the reading "state" to:
|
|
# - "error" if the last http request has shown an error condition
|
|
# - "disabled" if the device is disabled
|
|
# - "active" otherwise
|
|
#
|
|
# 2.0.1 tolerate empty field in energyDetails response
|
|
#
|
|
# 2.1.0 generate daily readings at ~23:59 instead of 22:00
|
|
#
|
|
# 2.2.0 allow smaller values for attributes intervalAtDayTime and intervalAtNightTime
|
|
#
|
|
###############################################################################
|
|
|
|
sub SolarEdgeAPI_SetVersion($)
|
|
{
|
|
my ($hash) = @_;
|
|
$hash->{VERSION} = "2.2.0";
|
|
}
|
|
|
|
###############################################################################
|
|
# module interface functions
|
|
###############################################################################
|
|
|
|
sub SolarEdgeAPI_Initialize($)
|
|
{
|
|
my ($hash) = @_;
|
|
|
|
$hash->{GetFn} = "SolarEdgeAPI_Get";
|
|
$hash->{SetFn} = "SolarEdgeAPI_Set";
|
|
$hash->{DefFn} = "SolarEdgeAPI_Define";
|
|
$hash->{UndefFn} = "SolarEdgeAPI_Undef";
|
|
$hash->{AttrFn} = "SolarEdgeAPI_Attr";
|
|
$hash->{AttrList} = "intervalAtDayTime ".
|
|
"intervalAtNightTime ".
|
|
"dayTimeStartHour ".
|
|
"nightTimeStartHour ".
|
|
"disable:1 ".
|
|
"enableStatusReadings:1,0 ".
|
|
"enableAggregatesReadings:1,0 ".
|
|
"enableOverviewReadings:1,0 ".
|
|
"enableStorageReadings:1,0 ".
|
|
"enableDailyDetailsReadings:1,0 ".
|
|
"enableDailyStorageReadings:1,0 ".
|
|
"enableDailyAggregatesReadings:1,0 ".
|
|
"enableDailyOverviewReadings:1,0 ".
|
|
"enableDebugReadings:1,0 ".
|
|
$readingFnAttributes;
|
|
|
|
$hash->{FW_detailFn} = "SolarEdgeAPI_fhemwebFn";
|
|
}
|
|
|
|
sub SolarEdgeAPI_Define($$)
|
|
{
|
|
my ($hash, $def) = @_;
|
|
|
|
my @a = split( "[ \t][ \t]*", $def );
|
|
|
|
if (int(@a) != 4)
|
|
{
|
|
return "incorrect number of parameters: define <name> SolarEdgeAPI <API-Key> <Site-ID>";
|
|
}
|
|
|
|
if ($solarEdgeAPI_missingModul)
|
|
{
|
|
return "Cannot define a SolarEdgeAPI device. Perl modul $solarEdgeAPI_missingModul is missing.";
|
|
}
|
|
|
|
my $name = $a[0];
|
|
|
|
$hash->{APIKEY} = $a[2];
|
|
$hash->{SITEID} = $a[3];
|
|
|
|
$hash->{DEFAULT_DAY_TIME_INTERVAL} = 215;
|
|
$hash->{DEFAULT_NIGHT_TIME_INTERVAL} = 1200;
|
|
|
|
$hash->{DEFAULT_DAY_TIME_START_HOUR} = 6;
|
|
$hash->{DEFAULT_NIGHT_TIME_START_HOUR} = 22;
|
|
|
|
$hash->{PORT} = 80;
|
|
$hash->{NOTIFYDEV} = "global";
|
|
$hash->{actionQueue} = [];
|
|
|
|
SolarEdgeAPI_ResetDebugCounters($hash);
|
|
|
|
SolarEdgeAPI_SetVersion($hash);
|
|
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - defined";
|
|
|
|
my %paths = (
|
|
'status' => 'currentPowerFlow.json',
|
|
'aggregates' => 'energyDetails.json',
|
|
'overview' => 'overview.json',
|
|
'storage' => 'storageData.json',
|
|
'dailyDetails' => 'details.json',
|
|
'dailyStorage' => 'storageData.json',
|
|
'dailyOverview' => 'overview.json',
|
|
'dailyAggregates' => 'energyDetails.json'
|
|
);
|
|
$hash->{PATHS} = \%paths;
|
|
|
|
# remove any active timer
|
|
RemoveInternalTimer($hash);
|
|
|
|
# initiate periodic readings
|
|
InternalTimer(gettimeofday() + 60, 'SolarEdgeAPI_RestartHttpRequestTimers', $hash);
|
|
|
|
SolarEdgeAPI_UpdateState($hash, "active");
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub SolarEdgeAPI_ResetDebugCounters($)
|
|
{
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
$hash->{NUMBER_OF_REQUESTS} = 0;
|
|
$hash->{NUMBER_OF_GOOD_RESPONSES} = 0;
|
|
$hash->{NUMBER_OF_ERROR_1} = 0;
|
|
$hash->{NUMBER_OF_ERROR_2} = 0;
|
|
$hash->{NUMBER_OF_ERROR_3} = 0;
|
|
$hash->{NUMBER_OF_JSON_ERRORS} = 0;
|
|
$hash->{NUMBER_OF_REQUESTS_PER_DAY} = 0;
|
|
|
|
if (AttrVal($name, "enableDebugReadings", undef))
|
|
{
|
|
readingsSingleUpdate($hash, 'debugNumRequests', $hash->{NUMBER_OF_REQUESTS}, 1);
|
|
readingsSingleUpdate($hash, 'debugNumGoodResponses', $hash->{NUMBER_OF_GOOD_RESPONSES}, 1);
|
|
readingsSingleUpdate($hash, 'debugNumJsonErrors', $hash->{NUMBER_OF_JSON_ERRORS}, 1);
|
|
readingsSingleUpdate($hash, 'debugNumError1', $hash->{NUMBER_OF_ERROR_1}, 1);
|
|
readingsSingleUpdate($hash, 'debugNumError2', $hash->{NUMBER_OF_ERROR_2}, 1);
|
|
readingsSingleUpdate($hash, 'debugNumError3', $hash->{NUMBER_OF_ERROR_3}, 1);
|
|
}
|
|
}
|
|
|
|
sub SolarEdgeAPI_Undef($$)
|
|
{
|
|
my ($hash, $arg) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - deleted";
|
|
|
|
# remove any active timer
|
|
RemoveInternalTimer($hash);
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub SolarEdgeAPI_Attr(@)
|
|
{
|
|
my ($cmd, $name, $attrName, $attrVal) = @_;
|
|
my $hash = $defs{$name};
|
|
|
|
if ($attrName eq "disable")
|
|
{
|
|
if ($cmd eq "set")
|
|
{
|
|
if ($attrVal eq "1")
|
|
{
|
|
RemoveInternalTimer($hash);
|
|
SolarEdgeAPI_UpdateState($hash, "disabled");
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute disable=1";
|
|
}
|
|
elsif ($attrVal eq "0")
|
|
{
|
|
InternalTimer(gettimeofday() + 5, 'SolarEdgeAPI_RestartHttpRequestTimers', $hash);
|
|
SolarEdgeAPI_UpdateState($hash, "active");
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute disable=0";
|
|
}
|
|
else
|
|
{
|
|
my $message = "unexpected value for attribute disable";
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - ".$message;
|
|
return $message;
|
|
}
|
|
}
|
|
elsif ($cmd eq "del")
|
|
{
|
|
InternalTimer(gettimeofday() + 5, 'SolarEdgeAPI_RestartHttpRequestTimers', $hash);
|
|
SolarEdgeAPI_UpdateState($hash, "active");
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute disable deleted";
|
|
}
|
|
}
|
|
|
|
if ($attrName eq "disabledForIntervals")
|
|
{
|
|
if ($cmd eq "set")
|
|
{
|
|
return "check disabledForIntervals Syntax HH:MM-HH:MM or 'HH:MM-HH:MM HH:MM-HH:MM ...'"
|
|
unless($attrVal =~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/);
|
|
SolarEdgeAPI_UpdateState($hash, "disabled");
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute disabledForIntervals set";
|
|
}
|
|
elsif ($cmd eq "del")
|
|
{
|
|
SolarEdgeAPI_UpdateState($hash, "active");
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute disabledForIntervals deleted";
|
|
}
|
|
}
|
|
|
|
if ($attrName eq "intervalAtDayTime")
|
|
{
|
|
if ($cmd eq "set")
|
|
{
|
|
if (($attrVal eq "auto") || ($attrVal >= 1))
|
|
{
|
|
InternalTimer(gettimeofday() + 5, 'SolarEdgeAPI_RestartHttpRequestTimers', $hash);
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute intervalAtDayTime set to $attrVal";
|
|
}
|
|
else
|
|
{
|
|
my $message = "intervalAtDayTime is out of range";
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - ".$message;
|
|
return $message;
|
|
}
|
|
}
|
|
elsif ($cmd eq "del")
|
|
{
|
|
InternalTimer(gettimeofday() + 5, 'SolarEdgeAPI_RestartHttpRequestTimers', $hash);
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute intervalAtDayTime deleted";
|
|
}
|
|
}
|
|
|
|
if ($attrName eq "intervalAtNightTime")
|
|
{
|
|
if ($cmd eq "set")
|
|
{
|
|
if (($attrVal < 1) or ($attrVal > 3600))
|
|
{
|
|
my $message = "intervalAtNightTime is out of range";
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - ".$message;
|
|
return $message;
|
|
}
|
|
else
|
|
{
|
|
InternalTimer(gettimeofday() + 5, 'SolarEdgeAPI_RestartHttpRequestTimers', $hash);
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute intervalAtNightTime set to $attrVal";
|
|
}
|
|
}
|
|
elsif ($cmd eq "del")
|
|
{
|
|
InternalTimer(gettimeofday() + 5, 'SolarEdgeAPI_RestartHttpRequestTimers', $hash);
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute intervalAtNightTime deleted";
|
|
}
|
|
}
|
|
|
|
if ($attrName eq "dayTimeStartHour")
|
|
{
|
|
if ($cmd eq "set")
|
|
{
|
|
if (($attrVal < 3) or ($attrVal > 10))
|
|
{
|
|
my $message = "dayTimeStartHour is out of range";
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - ".$message;
|
|
return $message;
|
|
}
|
|
else
|
|
{
|
|
InternalTimer(gettimeofday() + 5, 'SolarEdgeAPI_RestartHttpRequestTimers', $hash);
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute dayTimeStartHour set to $attrVal";
|
|
}
|
|
}
|
|
elsif ($cmd eq "del")
|
|
{
|
|
InternalTimer(gettimeofday() + 5, 'SolarEdgeAPI_RestartHttpRequestTimers', $hash);
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute dayTimeStartHour deleted";
|
|
}
|
|
}
|
|
|
|
if ($attrName eq "nightTimeStartHour")
|
|
{
|
|
if ($cmd eq "set")
|
|
{
|
|
if (($attrVal < 14) or ($attrVal > 22))
|
|
{
|
|
my $message = "nightTimeStartHour is out of range";
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - ".$message;
|
|
return $message;
|
|
}
|
|
else
|
|
{
|
|
InternalTimer(gettimeofday() + 5, 'SolarEdgeAPI_RestartHttpRequestTimers', $hash);
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute nightTimeStartHour set to $attrVal";
|
|
}
|
|
}
|
|
elsif ($cmd eq "del")
|
|
{
|
|
InternalTimer(gettimeofday() + 5, 'SolarEdgeAPI_RestartHttpRequestTimers', $hash);
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - attribute nightTimeStartHour deleted";
|
|
}
|
|
}
|
|
|
|
if (($attrName eq "enableStatusReadings") or
|
|
($attrName eq "enableAggregatesReadings") or
|
|
($attrName eq "enableOverviewReadings") or
|
|
($attrName eq "enableStorageReadings") or
|
|
($attrName eq "enableDailyDetailsReadings") or
|
|
($attrName eq "enableDailyStorageReadings") or
|
|
($attrName eq "enableDailyOverviewReadings") or
|
|
($attrName eq "enableDailyAggregatesReadings"))
|
|
{
|
|
if($cmd eq "set")
|
|
{
|
|
if (not (($attrVal eq "0") || ($attrVal eq "1")))
|
|
{
|
|
my $message = "illegal value for $attrName";
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - ".$message;
|
|
return $message;
|
|
}
|
|
else
|
|
{
|
|
InternalTimer(gettimeofday() + 5, 'SolarEdgeAPI_RestartHttpRequestTimers', $hash);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($attrName eq "enableDebugReadings")
|
|
{
|
|
if($cmd eq "set")
|
|
{
|
|
if (not (($attrVal eq "0") || ($attrVal eq "1")))
|
|
{
|
|
my $message = "illegal value for enableDebugReadings";
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - ".$message;
|
|
return $message;
|
|
}
|
|
}
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub SolarEdgeAPI_Set($$)
|
|
{
|
|
my ($hash, @parameters) = @_;
|
|
my $name = $parameters[0];
|
|
my $what = $parameters[1];
|
|
|
|
if ($what eq "restartTimer")
|
|
{
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - set restartTimer";
|
|
SolarEdgeAPI_RestartHttpRequestTimers($hash);
|
|
}
|
|
elsif ($what eq "resetDebugCounters")
|
|
{
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - set resetDebugCounters";
|
|
SolarEdgeAPI_ResetDebugCounters($hash);
|
|
}
|
|
elsif ($what eq "?")
|
|
{
|
|
my $message = "unknown argument $what, choose one of restartTimer:noArg resetDebugCounters:noArg";
|
|
return $message;
|
|
}
|
|
else
|
|
{
|
|
my $message = "unknown argument $what, choose one of restartTimer resetDebugCounters";
|
|
Log3 $name, 1, "SolarEdgeAPI ($name) - ".$message;
|
|
return $message;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub SolarEdgeAPI_Get($@)
|
|
{
|
|
my ($hash, $name, $cmd) = @_;
|
|
|
|
if (($cmd eq 'status') or ($cmd eq 'aggregates') or ($cmd eq 'overview') or ($cmd eq 'dailyOverview') or
|
|
($cmd eq 'storage') or ($cmd eq 'dailyDetails') or ($cmd eq 'dailyStorage') or ($cmd eq 'dailyAggregates'))
|
|
{
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - get command: ".$cmd;
|
|
|
|
if ((defined($hash->{actionQueue})) and (scalar(@{$hash->{actionQueue}}) > 0))
|
|
{
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - get command ".$cmd." ignored because actionQueue is not empty";
|
|
return 'There are still path commands in the action queue';
|
|
}
|
|
unshift( @{$hash->{actionQueue}}, $cmd );
|
|
SolarEdgeAPI_SendHttpRequest($hash);
|
|
}
|
|
elsif ($cmd eq 'numberOfRequests')
|
|
{
|
|
my $daytimeInterval = AttrVal($name, "intervalAtDayTime", $hash->{DEFAULT_DAY_TIME_INTERVAL});
|
|
my $nighttimeInterval = AttrVal($name, "intervalAtNightTime", $hash->{DEFAULT_NIGHT_TIME_INTERVAL});
|
|
|
|
my $dayTimeStartHour = AttrVal($name, "dayTimeStartHour", $hash->{DEFAULT_DAY_TIME_START_HOUR});
|
|
my $nightTimeStartHour = AttrVal($name, "nightTimeStartHour", $hash->{DEFAULT_NIGHT_TIME_START_HOUR});
|
|
my $numberOfDaytimeHours = $nightTimeStartHour - $dayTimeStartHour;
|
|
my $numberOfNighttimeHours = 24 - $numberOfDaytimeHours;
|
|
|
|
my $numberOfPeriodicHttpRequests = 0;
|
|
if (AttrVal($name, "enableStatusReadings", 0)) { $numberOfPeriodicHttpRequests += 1; }
|
|
if (AttrVal($name, "enableAggregatesReadings", 0)) { $numberOfPeriodicHttpRequests += 1; }
|
|
if (AttrVal($name, "enableOverviewReadings", 1)) { $numberOfPeriodicHttpRequests += 1; }
|
|
if (AttrVal($name, "enableStorageReadings", 0)) { $numberOfPeriodicHttpRequests += 1; }
|
|
|
|
$hash->{NUMBER_OF_REQUESTS_PER_DAY} =
|
|
(($numberOfDaytimeHours * 3600 / $daytimeInterval + $numberOfNighttimeHours * 3600 / $nighttimeInterval)
|
|
* $numberOfPeriodicHttpRequests)
|
|
+ (AttrVal($name, "enableDailyStorageReadings", 0))
|
|
+ (AttrVal($name, "enableDailyOverviewReadings", 1))
|
|
+ (AttrVal($name, "enableDailyDetailsReadings", 1))
|
|
+ (AttrVal($name, "enableDailyAggregatesReadings", 0));
|
|
|
|
return $hash->{NUMBER_OF_REQUESTS_PER_DAY};
|
|
}
|
|
else
|
|
{
|
|
my $list = 'status:noArg aggregates:noArg overview:noArg dailyOverview:noArg storage:noArg dailyDetails:noArg dailyStorage:noArg dailyAggregates:noArg numberOfRequests:noArg';
|
|
return "Unknown argument $cmd, choose one of $list";
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
###############################################################################
|
|
# HTTP request generation
|
|
###############################################################################
|
|
|
|
# precondition: There must be at least one entry in actionQueue.
|
|
sub SolarEdgeAPI_SendHttpRequest($)
|
|
{
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my $siteid = $hash->{SITEID};
|
|
my $host = "monitoringapi.solaredge.com/site/".$siteid;
|
|
my $apikey = $hash->{APIKEY};
|
|
my $path = pop(@{$hash->{actionQueue}});
|
|
|
|
# some API require additional parameters, e.g. the time frame and time
|
|
# resolution to use with the query
|
|
my $params = "";
|
|
if ($path eq "aggregates")
|
|
{
|
|
# request data for the timeframe from midnight until now
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
|
|
$params = "&timeUnit=QUARTER_OF_AN_HOUR&startTime=".(1900+$year)."-".(1+$mon)."-".$mday."%2000:00:00&endTime=".(1900+$year)."-".(1+$mon)."-".$mday."%20".$hour.":".$min.":".$sec;
|
|
}
|
|
elsif ($path eq "dailyAggregates")
|
|
{
|
|
# request data for the timeframe from January 1st until today
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
|
|
$params = "&timeUnit=DAY&startTime=".(1900+$year)."-1-1"."%20"."00:00:00&endTime=".(1900+$year)."-".(1+$mon)."-".$mday."%20".$hour.":".$min.":".$sec;
|
|
}
|
|
elsif (($path eq "storage") or ($path eq "dailyStorage"))
|
|
{
|
|
# request data for the last 1/2 hour
|
|
my ($sec1,$min1,$hour1,$mday1,$mon1,$year1,$wday1,$yday1,$isdst1) = localtime(time());
|
|
my ($sec2,$min2,$hour2,$mday2,$mon2,$year2,$wday2,$yday2,$isdst2) = localtime(time() - (30 * 60));
|
|
$params = "&startTime=".(1900+$year2)."-".(1+$mon2)."-".$mday2."%20".$hour2.":".$min2.":".$sec2.
|
|
"&endTime=".(1900+$year1)."-".(1+$mon1)."-".$mday1."%20".$hour1.":".$min1.":".$sec1;
|
|
}
|
|
|
|
my $pathsRef = $hash->{PATHS};
|
|
my %paths = %$pathsRef;
|
|
|
|
my $uri = $host . '/' . $paths{$path} . "?api_key=" . $apikey.$params;
|
|
|
|
HttpUtils_NonblockingGet(
|
|
{
|
|
url => "https://".$uri,
|
|
timeout => 5,
|
|
method => 'GET',
|
|
hash => $hash,
|
|
setCmd => $path,
|
|
doTrigger => 1,
|
|
callback => \&SolarEdgeAPI_HandleHttpResponse,
|
|
}
|
|
);
|
|
|
|
# update debug counter
|
|
$hash->{NUMBER_OF_REQUESTS} = $hash->{NUMBER_OF_REQUESTS} + 1;
|
|
if (AttrVal($name, "enableDebugReadings", undef))
|
|
{
|
|
readingsSingleUpdate($hash, 'debugNumRequests', $hash->{NUMBER_OF_REQUESTS}, 1);
|
|
}
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - SolarEdgeAPI_SendHttpRequest path: $path / $paths{$path}";
|
|
Log3 $name, 5, "SolarEdgeAPI ($name) - request: http://$uri";
|
|
}
|
|
|
|
sub SolarEdgeAPI_PeriodicHttpRequestTimerFunction($)
|
|
{
|
|
my $hash = shift;
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - periodic timer expired";
|
|
|
|
my $pathsRef = $hash->{PATHS};
|
|
my %paths = %$pathsRef;
|
|
|
|
if ((defined($hash->{actionQueue})) and (scalar(@{$hash->{actionQueue}}) < 100))
|
|
{
|
|
if (not IsDisabled($name))
|
|
{
|
|
while (my $obj = each %paths)
|
|
{
|
|
if ((($obj eq "status") and (AttrVal($name, "enableStatusReadings", 0))) or
|
|
(($obj eq "aggregates") and (AttrVal($name, "enableAggregatesReadings", 0))) or
|
|
(($obj eq "overview") and (AttrVal($name, "enableOverviewReadings", 1))) or
|
|
(($obj eq "storage") and (AttrVal($name, "enableStorageReadings", 0))))
|
|
{
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - adding periodic request to actionQueue: ".$obj;
|
|
unshift( @{$hash->{actionQueue}}, $obj );
|
|
}
|
|
}
|
|
SolarEdgeAPI_SendHttpRequest($hash);
|
|
}
|
|
else
|
|
{
|
|
SolarEdgeAPI_UpdateState($hash, "disabled");
|
|
}
|
|
}
|
|
|
|
InternalTimer(SolarEdgeAPI_GetTimeOfNextReading($hash), 'SolarEdgeAPI_PeriodicHttpRequestTimerFunction', $hash);
|
|
}
|
|
|
|
sub SolarEdgeAPI_DailyHttpRequestTimerFunction($)
|
|
{
|
|
my $hash = shift;
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - daily timer expired";
|
|
|
|
my $pathsRef = $hash->{PATHS};
|
|
my %paths = %$pathsRef;
|
|
|
|
if ((defined($hash->{actionQueue})) and (scalar(@{$hash->{actionQueue}}) < 100))
|
|
{
|
|
if (not IsDisabled($name))
|
|
{
|
|
while (my $obj = each %paths)
|
|
{
|
|
if ((($obj eq "dailyDetails") and (AttrVal($name, "enableDailyDetailsReadings", 1))) or
|
|
(($obj eq "dailyStorage") and (AttrVal($name, "enableDailyStorageReadings", 0))) or
|
|
(($obj eq "dailyOverview") and (AttrVal($name, "enableDailyOverviewReadings", 1))) or
|
|
(($obj eq "dailyAggregates") and (AttrVal($name, "enableDailyAggregatesReadings", 0))))
|
|
{
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - adding daily request to actionQueue: ".$obj;
|
|
unshift( @{$hash->{actionQueue}}, $obj );
|
|
}
|
|
}
|
|
SolarEdgeAPI_SendHttpRequest($hash);
|
|
}
|
|
else
|
|
{
|
|
SolarEdgeAPI_UpdateState($hash, "disabled");
|
|
}
|
|
}
|
|
|
|
InternalTimer(SolarEdgeAPI_GetTimeOfNextDailyReading($hash), 'SolarEdgeAPI_DailyHttpRequestTimerFunction', $hash);
|
|
}
|
|
|
|
sub SolarEdgeAPI_RestartHttpRequestTimers($)
|
|
{
|
|
my $hash = shift;
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - restarting timer";
|
|
|
|
# remove all active timers
|
|
RemoveInternalTimer($hash);
|
|
|
|
# Do the next http request now. This will start a timer for the next one.
|
|
SolarEdgeAPI_PeriodicHttpRequestTimerFunction($hash);
|
|
|
|
# Schedule the first daily request now. This will start a timer for the next one.
|
|
InternalTimer(SolarEdgeAPI_GetTimeOfNextDailyReading($hash), 'SolarEdgeAPI_DailyHttpRequestTimerFunction', $hash);
|
|
}
|
|
|
|
sub SolarEdgeAPI_GetTimeOfNextDailyReading($)
|
|
{
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my $epoch = time();
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($epoch);
|
|
|
|
if (($hour >= 23) and ($min >= 59))
|
|
{
|
|
# If it is 23:59 the next reading should occur tomorrow.
|
|
|
|
# add 24 hours to epoch to get a time during the following day
|
|
$epoch += 24 * 60 * 60;
|
|
|
|
# convert again
|
|
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($epoch);
|
|
}
|
|
|
|
# change hour:minute to 23:59 and convert to epoch
|
|
$epoch = fhemTimeLocal(0, 59, 23, $mday, $mon, $year); # $sec, $min, $hour, $mday, $month, $year
|
|
|
|
return $epoch;
|
|
}
|
|
|
|
sub SolarEdgeAPI_GetTimeOfNextReading($)
|
|
{
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
|
|
|
|
my $dayTimeStartHour = AttrVal($name, "dayTimeStartHour", $hash->{DEFAULT_DAY_TIME_START_HOUR});
|
|
my $nightTimeStartHour = AttrVal($name, "nightTimeStartHour", $hash->{DEFAULT_NIGHT_TIME_START_HOUR});
|
|
|
|
my $daytimeInterval = AttrVal($name, "intervalAtDayTime", $hash->{DEFAULT_DAY_TIME_INTERVAL});
|
|
my $nighttimeInterval = AttrVal($name, "intervalAtNightTime", $hash->{DEFAULT_NIGHT_TIME_INTERVAL});
|
|
|
|
# select the interval to use now
|
|
|
|
my $interval;
|
|
if (($hour >= $dayTimeStartHour) && ($hour < $nightTimeStartHour))
|
|
{
|
|
$interval = $daytimeInterval;
|
|
}
|
|
else
|
|
{
|
|
$interval = $nighttimeInterval;
|
|
}
|
|
|
|
# TODO if the next night time interval ends after dayTimeStartHour change interval so
|
|
# that the next request goes out at dayTimeStartHour
|
|
|
|
my $newTriggerTime = gettimeofday() + $interval;
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - next reading in $interval seconds";
|
|
|
|
return $newTriggerTime;
|
|
}
|
|
|
|
###############################################################################
|
|
# HTTP response handling
|
|
###############################################################################
|
|
|
|
sub SolarEdgeAPI_CheckHttpError($$$$)
|
|
{
|
|
my ($hash, $param, $err, $data) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
if (defined($err) and ($err ne ""))
|
|
{
|
|
# update debug counter
|
|
$hash->{NUMBER_OF_ERROR_1} = $hash->{NUMBER_OF_ERROR_1} + 1;
|
|
if (AttrVal($name, "enableDebugReadings", undef))
|
|
{
|
|
readingsSingleUpdate($hash, 'debugNumError1', $hash->{NUMBER_OF_ERROR_1}, 1);
|
|
}
|
|
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - error (1) in http response: $err";
|
|
SolarEdgeAPI_UpdateState($hash, "error");
|
|
|
|
# drop all outstanding requests
|
|
$hash->{actionQueue} = [];
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (($data eq "") and (exists($param->{code})) and ($param->{code} ne 200))
|
|
{
|
|
# update debug counter
|
|
$hash->{NUMBER_OF_ERROR_2} = $hash->{NUMBER_OF_ERROR_2} + 1;
|
|
if (AttrVal($name, "enableDebugReadings", undef))
|
|
{
|
|
readingsSingleUpdate($hash, 'debugNumError2', $hash->{NUMBER_OF_ERROR_2}, 1);
|
|
}
|
|
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - error (2) in http response, no data, code: ".$param->{code};
|
|
SolarEdgeAPI_UpdateState($hash, "error");
|
|
|
|
# drop all outstanding requests
|
|
$hash->{actionQueue} = [];
|
|
|
|
return 2;
|
|
}
|
|
|
|
if (($data =~ /Error/i) and (exists( $param->{code})))
|
|
{
|
|
# update debug counter
|
|
$hash->{NUMBER_OF_ERROR_3} = $hash->{NUMBER_OF_ERROR_3} + 1;
|
|
if (AttrVal($name, "enableDebugReadings", undef))
|
|
{
|
|
readingsSingleUpdate($hash, 'debugNumError3', $hash->{NUMBER_OF_ERROR_3}, 1);
|
|
}
|
|
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - error (3) in http response, code: ".$param->{code};
|
|
SolarEdgeAPI_UpdateState($hash, "error");
|
|
|
|
# drop all outstanding requests
|
|
$hash->{actionQueue} = [];
|
|
|
|
return 3;
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub SolarEdgeAPI_HandleHttpResponse($$$)
|
|
{
|
|
my ($param, $err, $data) = @_;
|
|
my $hash = $param->{hash};
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - SolarEdgeAPI_HandleHttpResponse";
|
|
|
|
if (SolarEdgeAPI_CheckHttpError($hash, $param, $err, $data))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Log3 $name, 5, "SolarEdgeAPI ($name) - received JSON data: $data";
|
|
|
|
# update debug counter
|
|
$hash->{NUMBER_OF_GOOD_RESPONSES} = $hash->{NUMBER_OF_GOOD_RESPONSES} + 1;
|
|
if (AttrVal($name, "enableDebugReadings", undef))
|
|
{
|
|
readingsSingleUpdate($hash, 'debugNumGoodResponses', $hash->{NUMBER_OF_GOOD_RESPONSES}, 1);
|
|
}
|
|
|
|
SolarEdgeAPI_ProcessResponse($hash, $param->{setCmd}, $data);
|
|
|
|
if (defined($hash->{actionQueue}) and scalar(@{$hash->{actionQueue}}) > 0)
|
|
{
|
|
SolarEdgeAPI_SendHttpRequest($hash);
|
|
}
|
|
}
|
|
|
|
sub SolarEdgeAPI_ProcessResponse($$$)
|
|
{
|
|
my ($hash, $path, $data) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - SolarEdgeAPI_ProcessResponse: $path";
|
|
|
|
my $readings;
|
|
|
|
# generate fake data for storage data API for debug purposes
|
|
my $generateFakeStorageData = 0;
|
|
if ((($path eq 'storage') or ($path eq 'dailyStorage')) and ($generateFakeStorageData))
|
|
{
|
|
$data = '{"storageData":{"batteryCount":1,"batteries":[{
|
|
"nameplate":9800.0,"serialNumber":"R155XXX","modelNumber":"R155XXX","telemetryCount":4,"telemetries":
|
|
[{"timeStamp":"2019-11-15 00:02:35","power":100.0,"batteryState":3,"lifeTimeEnergyDischarged":2275121,"lifeTimeEnergyCharged":2646795,"batteryPercentageState":8.999232,"fullPackEnergyAvailable":9999.0,"internalTemp":21.1,"ACGridCharging":0.0},
|
|
{"timeStamp":"2019-11-15 00:07:34","power":100.0,"batteryState":3,"lifeTimeEnergyDischarged":2275122,"lifeTimeEnergyCharged":2646795,"batteryPercentageState":8.999232,"fullPackEnergyAvailable":9999.0,"internalTemp":21.0,"ACGridCharging":0.0},
|
|
{"timeStamp":"2019-11-15 00:12:34","power":100.0,"batteryState":3,"lifeTimeEnergyDischarged":2275123,"lifeTimeEnergyCharged":2646795,"batteryPercentageState":8.999232,"fullPackEnergyAvailable":9999.0,"internalTemp":21.0,"ACGridCharging":0.0},
|
|
{"timeStamp":"2019-11-15 00:17:33","power":100.0,"batteryState":3,"lifeTimeEnergyDischarged":2276198,"lifeTimeEnergyCharged":2648149,"batteryPercentageState":10.99419,"fullPackEnergyAvailable":9999.0,"internalTemp":20.6,"ACGridCharging":0.0}
|
|
]}]}}';
|
|
}
|
|
|
|
my $decodedJsonData = eval{decode_json($data)};
|
|
|
|
if ($@)
|
|
{
|
|
# update debug counter
|
|
$hash->{NUMBER_OF_JSON_ERRORS} = $hash->{NUMBER_OF_JSON_ERRORS} + 1;
|
|
if (AttrVal($name, "enableDebugReadings", undef))
|
|
{
|
|
readingsSingleUpdate($hash, 'debugNumJsonErrors', $hash->{NUMBER_OF_JSON_ERRORS}, 1);
|
|
}
|
|
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - JSON error: $@";
|
|
SolarEdgeAPI_UpdateState($hash, "error");
|
|
|
|
return;
|
|
}
|
|
|
|
if ($path eq 'aggregates')
|
|
{
|
|
$readings = SolarEdgeAPI_ReadingsProcessing_Aggregates($hash, $decodedJsonData);
|
|
}
|
|
elsif ($path eq 'status')
|
|
{
|
|
$readings = SolarEdgeAPI_ReadingsProcessing_Status($hash, $decodedJsonData);
|
|
}
|
|
elsif ($path eq 'overview')
|
|
{
|
|
$readings = SolarEdgeAPI_ReadingsProcessing_Overview($hash, $decodedJsonData, 0);
|
|
}
|
|
elsif ($path eq 'storage')
|
|
{
|
|
$readings = SolarEdgeAPI_ReadingsProcessing_Storage($hash, $decodedJsonData, 0);
|
|
}
|
|
elsif ($path eq 'dailyDetails')
|
|
{
|
|
$readings = SolarEdgeAPI_ReadingsProcessing_DailyDetails($hash, $decodedJsonData);
|
|
}
|
|
elsif ($path eq 'dailyStorage')
|
|
{
|
|
$readings = SolarEdgeAPI_ReadingsProcessing_Storage($hash, $decodedJsonData, 1);
|
|
}
|
|
elsif ($path eq 'dailyOverview')
|
|
{
|
|
$readings = SolarEdgeAPI_ReadingsProcessing_Overview($hash, $decodedJsonData, 1);
|
|
}
|
|
elsif ($path eq 'dailyAggregates')
|
|
{
|
|
$readings = SolarEdgeAPI_ReadingsProcessing_DailyAggregates($hash, $decodedJsonData);
|
|
}
|
|
else
|
|
{
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - unknown type of response: $path";
|
|
}
|
|
|
|
SolarEdgeAPI_UpdateReadings($hash, $path, $readings);
|
|
|
|
SolarEdgeAPI_UpdateState($hash, "active");
|
|
}
|
|
|
|
sub SolarEdgeAPI_ReadingsProcessing_Aggregates($$)
|
|
{
|
|
my ($hash, $decodedJsonData) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my %readings;
|
|
|
|
if (not (ref($decodedJsonData) eq "HASH"))
|
|
{
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - aggregates response is not a hash";
|
|
return \%readings;
|
|
}
|
|
|
|
foreach my $meter ( @{$decodedJsonData->{'energyDetails'}->{'meters'}})
|
|
{
|
|
my $meterType = $meter->{'type'};
|
|
my $meterCum = 0;
|
|
my $meterRecent15Min = 0;
|
|
foreach my $meterData (@{$meter -> {'values'}})
|
|
{
|
|
my $value = $meterData->{'value'};
|
|
if (defined $value)
|
|
{
|
|
$meterCum = $meterCum + $value;
|
|
$meterRecent15Min = $value;
|
|
}
|
|
}
|
|
$readings{$meterType . "-cumToday"} = $meterCum;
|
|
$readings{$meterType . "-recent15min"} = $meterRecent15Min;
|
|
}
|
|
|
|
return \%readings;
|
|
}
|
|
|
|
sub SolarEdgeAPI_IsLastDayOfMonth($)
|
|
{
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my $isLastDayOfMonth = 0;
|
|
|
|
my $epoch = time();
|
|
my ($sec1, $min1, $hour1, $mday1, $mon1, $year1, $wday1, $yday1, $isdst1) = localtime($epoch);
|
|
my $epochOneDayLater = $epoch + 24 * 60 * 60;
|
|
my ($sec2, $min2, $hour2, $mday2, $mon2, $year2, $wday2, $yday2, $isdst2) = localtime($epochOneDayLater);
|
|
|
|
if ($mon1 != $mon2)
|
|
{
|
|
$isLastDayOfMonth = 1;
|
|
}
|
|
|
|
my $month = $mon1 + 1;
|
|
my $day = $mday1;
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - day $day month $month isLastDayOfMonth $isLastDayOfMonth";
|
|
|
|
return $isLastDayOfMonth;
|
|
}
|
|
|
|
sub SolarEdgeAPI_IsLastDayOfYear($)
|
|
{
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my $isLastDayOfYear = 0;
|
|
my $epoch = time();
|
|
my ($sec1, $min1, $hour1, $mday1, $mon1, $year1, $wday1, $yday1, $isdst1) = localtime($epoch);
|
|
my $epochOneDayLater = $epoch + 24 * 60 * 60;
|
|
my ($sec2, $min2, $hour2, $mday2, $mon2, $year2, $wday2, $yday2, $isdst2) = localtime($epochOneDayLater);
|
|
if ($year1 != $year2)
|
|
{
|
|
$isLastDayOfYear = 1;
|
|
}
|
|
my $month = $mon1 + 1;
|
|
my $day = $mday1;
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - day $day month $month isLastDayOfYear $isLastDayOfYear";
|
|
|
|
return $isLastDayOfYear;
|
|
}
|
|
|
|
|
|
sub SolarEdgeAPI_ReadingsProcessing_DailyAggregates($$)
|
|
{
|
|
my ($hash, $decodedJsonData) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my %readings;
|
|
|
|
if (not (ref($decodedJsonData) eq "HASH"))
|
|
{
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - daily aggregates response is not a hash";
|
|
return \%readings;
|
|
}
|
|
|
|
my ($sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst) = localtime(time());
|
|
$month = $month + 1;
|
|
|
|
# iterate over day for different meters
|
|
foreach my $meter ( @{$decodedJsonData->{'energyDetails'}->{'meters'}})
|
|
{
|
|
my $meterType = $meter->{'type'};
|
|
my $cumYear = 0;
|
|
my $cumMonth = 0;
|
|
my $cumToday = 0;
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - meterType $meterType";
|
|
|
|
# accumulate values of one meter
|
|
foreach my $meterData (@{$meter -> {'values'}})
|
|
{
|
|
my $value = $meterData->{'value'};
|
|
|
|
# decode timestamp, example: "2015-10-19 00:00:00"
|
|
my $timestamp = $meterData->{'date'};
|
|
my $timestampMonth = -1;
|
|
my $timestampDay = -1;
|
|
if (!($timestamp =~ m/^([0-9]+)\-([0-9]+)\-([0-9]+)/))
|
|
{
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - invalid timestamp in energyDetails response";
|
|
}
|
|
else
|
|
{
|
|
$timestampMonth = $2;
|
|
$timestampDay = $3;
|
|
}
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - $timestamp $value - timestampMonth $timestampMonth timestampDay $timestampDay";
|
|
|
|
# cumulate for all days of this year
|
|
$cumYear += $value;
|
|
|
|
# cumulate for all days of this month
|
|
if ($timestampMonth == $month)
|
|
{
|
|
$cumMonth += $value;
|
|
|
|
# detect the cumulated value for today
|
|
if ($timestampDay == $day)
|
|
{
|
|
$cumToday = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
$readings{$meterType."-cumYear"} = $cumYear / 1000.0;
|
|
$readings{$meterType."-cumMonth"} = $cumMonth / 1000.0;
|
|
$readings{$meterType."-cumDay"} = $cumToday;
|
|
|
|
if (SolarEdgeAPI_IsLastDayOfMonth($hash))
|
|
{
|
|
$readings{$meterType."-cumMonthOnce"} = $cumMonth / 1000.0;
|
|
}
|
|
if (SolarEdgeAPI_IsLastDayOfYear($hash))
|
|
{
|
|
$readings{$meterType."-cumYearOnce"} = $cumYear / 1000.0;
|
|
}
|
|
}
|
|
|
|
return \%readings;
|
|
}
|
|
|
|
sub SolarEdgeAPI_ReadingsProcessing_Status($$)
|
|
{
|
|
my ($hash, $decodedJsonData) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my %readings;
|
|
my $data = $decodedJsonData->{'siteCurrentPowerFlow'};
|
|
|
|
if ((defined $data) && (!defined $data->{'unit'}))
|
|
{
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - API currentPowerFlow is not supported. Avoid unsuccessful server queries by setting attribute enableStatusReadings=0.";
|
|
}
|
|
else
|
|
{
|
|
$readings{'unit'} = $data->{'unit'} || "Error Reading Response";
|
|
$readings{'updateRefreshRate'} = $data->{'updateRefreshRate'} || "Error Reading Response";
|
|
|
|
# Connections / Directions
|
|
my $pv2load = 0;
|
|
my $pv2storage = 0;
|
|
my $load2storage = 0;
|
|
my $storage2load = 0;
|
|
my $load2grid = 0;
|
|
my $grid2load = 0;
|
|
foreach my $connection ( @{ $data->{'connections'} }) {
|
|
my $from = lc($connection->{'from'});
|
|
my $to = lc($connection->{'to'});
|
|
if (($from eq 'grid') and ($to eq 'load')) { $grid2load = 1; }
|
|
if (($from eq 'load') and ($to eq 'grid')) { $load2grid = 1; }
|
|
if (($from eq 'load') and ($to eq 'storage')) { $load2storage = 1; }
|
|
if (($from eq 'pv') and ($to eq 'storage')) { $pv2storage = 1; }
|
|
if (($from eq 'pv') and ($to eq 'load')) { $pv2load = 1; }
|
|
if (($from eq 'storage') and ($to eq 'load')) { $storage2load = 1; }
|
|
}
|
|
|
|
# GRID
|
|
$readings{'grid_status'} = $data->{'GRID'}->{"status"} || "-";
|
|
$readings{'grid_power'} = (($load2grid > 0) ? "-" : "").$data->{'GRID'}->{"currentPower"};
|
|
|
|
# LOAD
|
|
$readings{'load_status'} = $data->{'LOAD'}->{"status"} || "-";
|
|
$readings{'load_power'} = $data->{'LOAD'}->{"currentPower"};
|
|
|
|
# PV
|
|
$readings{'pv_status'} = $data->{'PV'}->{"status"} || "-";
|
|
$readings{'pv_power'} = $data->{'PV'}->{"currentPower"};
|
|
|
|
# Storage
|
|
$readings{'storage_status'} = $data->{'STORAGE'}->{"status"} || "-";
|
|
if ($readings{'storage_status'} ne "-")
|
|
{
|
|
$readings{'storage_power'} = (($storage2load > 0) ? "-" : "").$data->{'STORAGE'}->{"currentPower"};
|
|
$readings{'storage_level'} = $data->{'STORAGE'}->{"chargeLevel"} || "-";
|
|
$readings{'storage_critical'} = $data->{'STORAGE'}->{"critical"};
|
|
}
|
|
}
|
|
|
|
return \%readings;
|
|
}
|
|
|
|
sub SolarEdgeAPI_ReadingsProcessing_Overview($$$)
|
|
{
|
|
my ($hash, $decodedJsonData, $daily) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my %readings;
|
|
my $data = $decodedJsonData->{'overview'};
|
|
|
|
if (not $daily)
|
|
{
|
|
$readings{'power'} = $data->{'currentPower'}->{"power"};
|
|
}
|
|
$readings{'energyLifetime'} = $data->{'lifeTimeData'}->{"energy"} / 1000.0 / 1000.0;
|
|
my $energyYear = $data->{'lastYearData'}->{"energy"} / 1000.0;
|
|
$readings{'energyYear'} = $energyYear;
|
|
|
|
my $energyMonth = $data->{'lastMonthData'}->{"energy"} / 1000.0;
|
|
$readings{'energyMonth'} = $energyMonth;
|
|
|
|
my $energyDay = $data->{'lastDayData'}->{"energy"};
|
|
$readings{'energyDay'} = $energyDay;
|
|
|
|
if (SolarEdgeAPI_IsLastDayOfMonth($hash))
|
|
{
|
|
$readings{"energyMonthOnce"} = $energyMonth;
|
|
}
|
|
if (SolarEdgeAPI_IsLastDayOfYear($hash))
|
|
{
|
|
$readings{"energyYearOnce"} = $energyYear;
|
|
}
|
|
|
|
return \%readings;
|
|
}
|
|
|
|
sub SolarEdgeAPI_decodeBatteryState($)
|
|
{
|
|
my ($code) = @_;
|
|
|
|
my $result = "$code"."_";
|
|
|
|
if ($code == 0) { $result .= "Off"; }
|
|
elsif ($code == 1) { $result .= "Standby"; }
|
|
elsif ($code == 2) { $result .= "Init"; }
|
|
elsif ($code == 3) { $result .= "Charge"; }
|
|
elsif ($code == 4) { $result .= "Discharge"; }
|
|
elsif ($code == 5) { $result .= "Fault"; }
|
|
elsif ($code == 7) { $result .= "Idle"; }
|
|
else { $result .= "Unknown"; }
|
|
|
|
return $result;
|
|
}
|
|
|
|
sub SolarEdgeAPI_ReadingsProcessing_Storage($$$)
|
|
{
|
|
my ($hash, $decodedJsonData, $daily) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my %readings;
|
|
|
|
if (not (ref($decodedJsonData) eq "HASH"))
|
|
{
|
|
Log3 $name, 3, "SolarEdgeAPI ($name) - storageData response is not a hash";
|
|
return \%readings;
|
|
}
|
|
|
|
foreach my $batteryData ( @{$decodedJsonData->{'storageData'}->{'batteries'}})
|
|
{
|
|
my $serialNumber = $batteryData->{'serialNumber'};
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - serialNumber $serialNumber";
|
|
|
|
my $power = 0;
|
|
my $batteryState = -1;
|
|
my $lifeTimeEnergyCharged = 0;
|
|
my $lifeTimeEnergyDischarged = 0;
|
|
my $fullPackEnergyAvailable = 0;
|
|
my $internalTemp = 0;
|
|
my $batteryPercentageState = 0;
|
|
my $acGridCharging = 0;
|
|
|
|
foreach my $dataset (@{$batteryData -> {'telemetries'}})
|
|
{
|
|
my $newPower = $dataset->{'power'};
|
|
my $newBatteryState = $dataset->{'batteryState'};
|
|
my $newLifeTimeEnergyCharged = $dataset->{'lifeTimeEnergyCharged'};
|
|
my $newLifeTimeEnergyDischarged = $dataset->{'lifeTimeEnergyDischarged'};
|
|
my $newFullPackEnergyAvailable = $dataset->{'fullPackEnergyAvailable'};
|
|
my $newInternalTemp = $dataset->{'internalTemp'};
|
|
my $newBatteryPercentageState = $dataset->{'batteryPercentageState'};
|
|
my $newAcGridCharging = $dataset->{'ACGridCharging'};
|
|
|
|
if (($newPower > -100000) and ($newPower < 100000)) { $power = $newPower; }
|
|
if (($newBatteryState >= 0) and ($newBatteryState <= 10)) { $batteryState = $newBatteryState; }
|
|
if ($newLifeTimeEnergyCharged > 0) { $lifeTimeEnergyCharged = $newLifeTimeEnergyCharged; }
|
|
if ($newLifeTimeEnergyDischarged > 0) { $lifeTimeEnergyDischarged = $newLifeTimeEnergyDischarged; }
|
|
if ($newFullPackEnergyAvailable > 0) { $fullPackEnergyAvailable = $newFullPackEnergyAvailable; }
|
|
if (($newInternalTemp > 0) and ($newInternalTemp < 200)) { $internalTemp = $newInternalTemp; }
|
|
if (($newBatteryPercentageState >= 0) and ($newBatteryPercentageState <= 100)) { $batteryPercentageState = $newBatteryPercentageState; }
|
|
if ($newAcGridCharging >= 0) { $acGridCharging = $newAcGridCharging; }
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - new: $newPower $newBatteryState $newLifeTimeEnergyCharged $newLifeTimeEnergyDischarged $newFullPackEnergyAvailable $newInternalTemp $newBatteryPercentageState $newAcGridCharging";
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - $power $batteryState $lifeTimeEnergyCharged $lifeTimeEnergyDischarged $fullPackEnergyAvailable $internalTemp $batteryPercentageState $acGridCharging";
|
|
}
|
|
|
|
if ($daily)
|
|
{
|
|
$readings{$serialNumber."-lifeTimeEnergyCharged"} = $lifeTimeEnergyCharged / 1000.0 / 1000.0;
|
|
$readings{$serialNumber."-lifeTimeEnergyDischarged"} = $lifeTimeEnergyDischarged / 1000.0 / 1000.0;
|
|
$readings{$serialNumber."-fullPackEnergyAvailable"} = $fullPackEnergyAvailable;
|
|
}
|
|
else
|
|
{
|
|
$readings{$serialNumber."-power"} = $power;
|
|
$readings{$serialNumber."-batteryState"} = $batteryState;
|
|
$readings{$serialNumber."-batteryStateDecoded"} = SolarEdgeAPI_decodeBatteryState($batteryState);
|
|
$readings{$serialNumber."-lifeTimeEnergyCharged"} = $lifeTimeEnergyCharged;
|
|
$readings{$serialNumber."-lifeTimeEnergyDischarged"} = $lifeTimeEnergyDischarged;
|
|
$readings{$serialNumber."-internalTemp"} = $internalTemp;
|
|
$readings{$serialNumber."-batteryPercentageState"} = $batteryPercentageState;
|
|
$readings{$serialNumber."-ACGridCharging"} = $acGridCharging;
|
|
}
|
|
}
|
|
|
|
return \%readings;
|
|
}
|
|
|
|
sub SolarEdgeAPI_ReadingsProcessing_DailyDetails($$)
|
|
{
|
|
my ($hash, $decodedJsonData) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my %readings;
|
|
my $data = $decodedJsonData->{'details'};
|
|
|
|
# documented but not in the response:
|
|
#$readings{'alertQuantity'} = $data->{'alertQuantity'};
|
|
#$readings{'alertSeverity'} = $data->{'alertSeverity'};
|
|
|
|
$readings{'peakPower'} = $data->{'peakPower'};
|
|
$readings{'status'} = $data->{'status'};
|
|
|
|
return \%readings;
|
|
}
|
|
|
|
sub SolarEdgeAPI_UpdateReadings($$$)
|
|
{
|
|
my ($hash, $path, $readings) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - SolarEdgeAPI_UpdateReadings";
|
|
|
|
readingsBeginUpdate($hash);
|
|
while (my ($r,$v) = each %{$readings})
|
|
{
|
|
readingsBulkUpdate($hash,$path.'-'.$r,$v);
|
|
}
|
|
readingsEndUpdate($hash, 1);
|
|
}
|
|
|
|
###############################################################################
|
|
# update "state"
|
|
###############################################################################
|
|
|
|
sub SolarEdgeAPI_UpdateState($$)
|
|
{
|
|
my ($hash, $newState) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 $name, 4, "SolarEdgeAPI ($name) - new state: $newState";
|
|
$hash->{STATE} = $newState;
|
|
readingsSingleUpdate($hash, "state", $newState, 1);
|
|
}
|
|
|
|
###############################################################################
|
|
# show SolarEdge logo
|
|
###############################################################################
|
|
|
|
sub SolarEdgeAPI_fhemwebFn($$$)
|
|
{
|
|
my ($FW_wname, $d, $room) = @_;
|
|
return << 'EOF'
|
|
<br>
|
|
<a href="https://www.solaredge.com">
|
|
<img src="https://www.solaredge.com/sites/default/files/SolarEdge_logo_header_new.png">
|
|
</a>
|
|
<br>
|
|
EOF
|
|
}
|
|
|
|
1;
|
|
|
|
=pod
|
|
=item device
|
|
=item summary Retrieves data from a SolarEdge PV system via the SolarEdge Monitoring API
|
|
=item summary_DE
|
|
=begin html
|
|
|
|
<a name="SolarEdgeAPI"></a>
|
|
<h3>SolarEdgeAPI</h3>
|
|
|
|
<ul>
|
|
This module retrieves data from a SolarEdge PV system via the SolarEdge Server Monitoring API.<br>
|
|
<br>
|
|
Data is retrieved from the server periodically. The interval during day time is typically higher<br>
|
|
compared to night time. According to the API documentation the total number of server queries per<br>
|
|
day is limited to 300.<br>
|
|
The intervals as well as the start of day time and night time can be controlled by attributes.<br>
|
|
In each interval each enabled group of readings is generated once. You can reduce the number of<br>
|
|
server queries by disabling groups of readings and by increasing the interval time.<br>
|
|
<br>
|
|
Note: Features marked as "deprecated" or "debug only" may change or disappear in future versions.<br>
|
|
<br>
|
|
|
|
<a name="SolarEdgeAPI_Define"></a>
|
|
<b>Define</b>
|
|
<ul>
|
|
<code>define <name> SolarEdgeAPI <API Key> <Site ID></code><br>
|
|
The <API Key> and the <Site ID> can be retrieved from the SolarEdge<br>
|
|
Monitoring Portal. The <API Key> has to be enabled in the "Admin" Secion<br>
|
|
of the web portal.<br>
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="SolarEdgeAPI_Readings"></a>
|
|
<b>Readings</b>
|
|
<ul>
|
|
All reading names start with the name of the group of readings followed by "-".<br>
|
|
All readings that belong to the same group have the same timing: Some groups of readings are generated<br>
|
|
periodically. The period is defined by attributes intervalAtDayTime, intervalAtNighttime, dayTimeStartHour and<br>
|
|
nightTimeStartHour. Other readings are generated once per day only. Reading groups which are updated<br>
|
|
once per day have a name starting with "daily". Each update of a group of readings requires on http<br>
|
|
request to the SolarEdge server. The number of queries is limited to 300 per day, according to API<br>
|
|
documentation.<br>
|
|
<br>
|
|
Groups of readings:<br>
|
|
<br>
|
|
|
|
<li>overview - readings generated from "overview" API response
|
|
<ul>
|
|
<li>overview-power [W]</li>
|
|
<li>overview-energyLifetime [MWh]</li>
|
|
<li>overview-energyYear [kWh]</li>
|
|
<li>overview-energyMonth [kWh]</li>
|
|
<li>overview-energyDay [Wh]</li>
|
|
</ul>
|
|
</li>
|
|
|
|
<li>dailyOverview - readings generated from "overview" API response once per day
|
|
<ul>
|
|
<li>dailyOverview-energyDay [Wh] - This reading is derived. It depends on the latest dailyOverview-energyMonth reading.</li>
|
|
<li>dailyOverview-energyMonth [kWh]</li>
|
|
<li>dailyOverview-energyYear [kWh]</li>
|
|
<li>dailyOverview-energyLifetime [MWh]</li>
|
|
<li>dailyOverview-energyMonthOnce [kWh] generated on the last day of the month only</li>
|
|
<li>dailyOverview-energyYearOnce [kWh] generated on the last day of the year only</li>
|
|
</ul>
|
|
</li>
|
|
|
|
<li>aggregates - readings generated from "energyDetails" API response<br>
|
|
<ul>
|
|
<li>aggregates-<meterType>-cumToday [Wh]</li>
|
|
<li>aggregates-<meterType>-recent15min [Wh](deprecated) </li>
|
|
</ul>
|
|
</li>
|
|
|
|
<li>dailyAggregates - readings generated from "energyDetails" API response once per day<br>
|
|
<ul>
|
|
<li>dailyAggregates-<meterType>-cumDayOnce [Wh]</li>
|
|
<li>dailyAggregates-<meterType>-cumMonthDaily [kWh]</li>
|
|
<li>dailyAggregates-<meterType>-cumYearDaily [kWh]</li>
|
|
<li>dailyAggregates-<meterType>-cumMonthOnce [kWh] generated on the last day of the month only</li>
|
|
<li>dailyAggregates-<meterType>-cumYearOnce [kWh] generated on the last day of the year only</li>
|
|
</ul>
|
|
</li>
|
|
|
|
<li>storage - readings generated from "storageData" API response<br>
|
|
<ul>
|
|
<li>storage-<serial>-power [W]</li>
|
|
<li>storage-<serial>-batteryState</li>
|
|
<li>storage-<serial>-batteryStateDecoded [text]</li>
|
|
<li>storage-<serial>-lifetimeEnergyCharged</li>
|
|
<li>storage-<serial>-lifetimeEnergyDischarged</li>
|
|
<li>storage-<serial>-internalTemp [degrees C]</li>
|
|
<li>storage-<serial>-batteryPercentageState [percent]</li>
|
|
</ul>
|
|
</li>
|
|
|
|
<li>dailyStorage - readings generated from "storageData" API response once per day<br>
|
|
<ul>
|
|
<li>dailyStorage-<serial>-lifetimeEnergyCharged [MWh]</li>
|
|
<li>dailyStorage-<serial>-lifetimeEnergyDischarged [MWh]</li>
|
|
<li>dailyStorage-<serial>-fullPackEnergyAvailable [kWh]</li>
|
|
</ul>
|
|
</li>
|
|
|
|
<li>dailyDetails - readings generated from "details" API response once per day<br>
|
|
<ul>
|
|
<li>dailyDetails-peakPower [W]</li>
|
|
<li>dailyDetails-status [text]</li>
|
|
</ul>
|
|
</li>
|
|
|
|
<li>status - readings generated from "currentPowerFlow" API response. This API is not supported by all sites.<br>
|
|
<ul>
|
|
<li>status-grid_status [?]</li>
|
|
<li>status-grid_power [W]</li>
|
|
<li>status-load_status [?]</li>
|
|
<li>status-load_power [W]</li>
|
|
<li>status-pv_status [?]</li>
|
|
<li>status-pv_power [W]</li>
|
|
<li>status-storage_status [?]</li>
|
|
<li>status-storage_power [W]</li>
|
|
<li>status-storage_level [?]</li>
|
|
<li>status-storage_critical [?]</li>
|
|
</ul>
|
|
</li>
|
|
|
|
<li>debug - debug data about successful and failing http requests (for debug only)</li>
|
|
<li>actionQueue - information about the entries in the action queue (for debug only)</li>
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="SolarEdgeAPI_Get"></a>
|
|
<b>Get</b>
|
|
<ul>
|
|
<li>numberOfRequests - get the expected number of requests per day with current attribute settings (for debug only)</li>
|
|
<li>status - fetch corresponding group of readings (for debug only)</li>
|
|
<li>aggregates - fetch corresponding group of readings (for debug only)</li>
|
|
<li>overview - fetch corresponding group of readings (for debug only)</li>
|
|
<li>storage - fetch corresponding group of readings (for debug only)</li>
|
|
<li>dailyDetails - fetch corresponding group of readings (for debug only)</li>
|
|
<li>dailyStorage - fetch corresponding group of readings (for debug only)</li>
|
|
<li>dailyAggregates - fetch corresponding group of readings (for debug only)</li>
|
|
<li>dailyOverview - fetch corresponding group of readings (for debug only)</li>
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="SolarEdgeAPI_Set"></a>
|
|
<b>Set</b>
|
|
<ul>
|
|
<li>restartTimer - restart periodic http requests (for debug only)</li>
|
|
<li>resetDebugCounters - reset debug counters (internals and optional debug* readings) (for debug only)</li>
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="SolarEdgeAPI_Attributes"></a>
|
|
<b>Attributes</b>
|
|
<ul>
|
|
<li>intervalAtDayTime - interval of http requests during day time (default: 215 (seconds))</li>
|
|
<li>intervalAtNightTime - interval of http requests during night time (default: 1200 (seconds))</li>
|
|
<li>dayTimeStartHour - start of daytime, default 6 (= 6:00am)</li>
|
|
<li>nightTimeStartHour - start of night time, default 22 (= 10:00pm)</li>
|
|
<li>enableStatusReadings - enable the corresponding group of readings, default: 0</li>
|
|
<li>enableAggregatesReadings - enable the corresponding group of readings, default: 0</li>
|
|
<li>enableOverviewReadings - enable the corresponding group of readings, default: 1</li>
|
|
<li>enableStorageReadings - enable the corresponding group of readings, default: 0</li>
|
|
<li>enableDailyDetailsReadings - enable the corresponding group of readings, default: 1</li>
|
|
<li>enableDailyStorageReadings - enable the corresponding group of readings, default: 0</li>
|
|
<li>enableDailyAggregatesReadings - enable the corresponding group of readings, default: 0</li>
|
|
<li>enableDailyOverviewReadings - enable the corresponding group of readings, default: 1</li>
|
|
<li>enableDebugReadings Enable the debug* readings. These debug readings do not cause additional http requests. Default: 0</li>
|
|
</ul>
|
|
<br>
|
|
|
|
</ul>
|
|
|
|
=end html
|
|
|
|
=cut
|