From 83737c4fe062b9bd1eeb56d61740357dd0c0c914 Mon Sep 17 00:00:00 2001 From: Olivier Mehani Date: Sun, 14 Jun 2020 22:58:37 +1000 Subject: [PATCH] [plugins/solar/fronius] Monitor Solar Inverters using the Fronius Solar API This includes caching of the inverter info with a 12h TTL, so the graph still renders when the inverter has gone to sleep at night. Signed-off-by: Olivier Mehani squash! [plugins/solar/fronius] Monitor Solar Inverters using the Fronius Solar API Use generic caching function to also cache real-time data. This allows to avoid N/As overnight, which don't aggregate nicely on the yearly graph. --- plugins/isp/internode_usage | 2 +- plugins/solar/example_graphs/fronius-week.png | Bin 0 -> 31267 bytes plugins/solar/fronius | 288 ++++++++++++++++++ 3 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 plugins/solar/example_graphs/fronius-week.png create mode 100755 plugins/solar/fronius diff --git a/plugins/isp/internode_usage b/plugins/isp/internode_usage index 93f51054..1d9cf035 100755 --- a/plugins/isp/internode_usage +++ b/plugins/isp/internode_usage @@ -38,7 +38,7 @@ multiple times, by symlinking it as 'internode_usage_SERVICEID'. =head1 CACHING As the API is sometimes flakey, the initial service information is cached -locally, with a one-hour lifetime, before hitting the base API again. However, +locally, with a day's lifetime, before hitting the base API again. However, if hitting the API to refresh the cache fails, the stale cache is used anyway, to have a better chance of getting the data out nonetheless. diff --git a/plugins/solar/example_graphs/fronius-week.png b/plugins/solar/example_graphs/fronius-week.png new file mode 100644 index 0000000000000000000000000000000000000000..1c56e0790ad513add2834f09bf91bcae0d519465 GIT binary patch literal 31267 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 literal 0 HcmV?d00001 diff --git a/plugins/solar/fronius b/plugins/solar/fronius new file mode 100755 index 00000000..c31cacce --- /dev/null +++ b/plugins/solar/fronius @@ -0,0 +1,288 @@ +#!/bin/sh +# -*- sh -*- + +: << =cut + +=head1 NAME + +fronius - Plugin to monitor Fronius Solar inverter using the JSON Solar API. + +The Solar API reports both an immediate power output reading at +time-of-request, and an incremental sum of daily and yearly produced energy. +This plugin uses the yearly energy sum as a DERIVE value, and calculates the +average power output during the last measurement interval. This will likely be +lower than the immediate reading, but the aggregation in weekly/monthly/yearly +graphs will be more correct. The immediate power output is output as extra +information. + +=head1 CONFIGURATION + + [fronius] + env.inverter_base_url http://fronius # this is the default + env.host_name solar_inverter # optional, host name to report data as in munin + env.connect_timeout 1 # optional, amount to wait for requests, in seconds + +=head1 CACHING + +As the inverter may go to sleep at night, the initial service information is cached +locally, with a twelve-hour lifetime, before hitting the Solar API again. However, +if hitting the API to refresh the cache fails, the stale cache is used anyway, +to have a better chance of getting the config data out nonetheless. + +=head1 CAVEAT + +Only tested on a Fronius Primo. + +=head1 AUTHOR + +Olivier Mehani + +Copyright (C) 2020 Olivier Mehani + +=head1 LICENSE + +SPDX-License-Identifier: GPL-3.0-or-later + +=head1 MAGIC MARKERS + + #%# family=manual + +=cut + +# Example outputs +# +## http://fronius/solar_api/v1/GetInverterInfo.cgi +#GetInverterInfo=' +#{ +# "Body" : { +# "Data" : { +# "1" : { +# "CustomName" : "Primo 5.0-1 (1)", +# "DT" : 76, +# "ErrorCode" : 0, +# "PVPower" : 5200, +# "Show" : 1, +# "StatusCode" : 7, +# "UniqueID" : "1098861" +# } +# } +# }, +# "Head" : { +# "RequestArguments" : {}, +# "Status" : { +# "Code" : 0, +# "Reason" : "", +# "UserMessage" : "" +# }, +# "Timestamp" : "2020-06-11T10:55:23+10:00" +# } +#} +#' +# +## http://fronius/solar_api/v1/GetPowerFlowRealtimeData.fcgi +#GetPowerFlowRealtimeData=' +#{ +# "Body" : { +# "Data" : { +# "Inverters" : { +# "1" : { +# "DT" : 76, +# "E_Day" : 1201, +# "E_Total" : 1201, +# "E_Year" : 1201.4000244140625, +# "P" : 2521 +# } +# }, +# "Site" : { +# "E_Day" : 1201, +# "E_Total" : 1201, +# "E_Year" : 1201.4000244140625, +# "Meter_Location" : "unknown", +# "Mode" : "produce-only", +# "P_Akku" : null, +# "P_Grid" : null, +# "P_Load" : null, +# "P_PV" : 2521, +# "rel_Autonomy" : null, +# "rel_SelfConsumption" : null +# }, +# "Version" : "12" +# } +# }, +# "Head" : { +# "RequestArguments" : {}, +# "Status" : { +# "Code" : 0, +# "Reason" : "", +# "UserMessage" : "" +# }, +# "Timestamp" : "2020-06-11T10:55:21+10:00" +# } +#} +#' +# +## http://fronius/solar_api/v1/GetActiveDeviceInfo.cgi?DeviceClass=SensorCard +#GetActiveDeviceInfo=' +#{ +# "Body" : { +# "Data" : {} +# }, +# "Head" : { +# "RequestArguments" : { +# "DeviceClass" : "SensorCard" +# }, +# "Status" : { +# "Code" : 0, +# "Reason" : "", +# "UserMessage" : "" +# }, +# "Timestamp" : "2020-06-11T10:55:24+10:00" +# } +#} +#' +# +## http://fronius/solar_api/v1/GetLoggerConnectionInfo.cgi +#GetLoggerConnectionInfo=' +#{ +# "Body" : { +# "Data" : { +# "SolarNetConnectionState" : 2, +# "SolarWebConnectionState" : 2, +# "WLANConnectionState" : 2 +# } +# }, +# "Head" : { +# "RequestArguments" : {}, +# "Status" : { +# "Code" : 0, +# "Reason" : "", +# "UserMessage" : "" +# }, +# "Timestamp" : "2020-06-11T10:55:25+10:00" +# } +#} +#' + +set -eu + +# shellcheck disable=SC1090 +. "${MUNIN_LIBDIR}/plugins/plugin.sh" + +if [ "${MUNIN_DEBUG:-0}" = 1 ]; then + set -x +fi + +INVERTER_BASE_URL=${inverter_base_url:-http://fronius} +HOST_NAME=${host_name:-} +CONNECT_TIMEOUT=${connect_timeout:-1} + +check_deps() { + for CMD in curl jq recode; do + if ! command -v "${CMD}" >/dev/null; then + echo "no (${CMD} not found)" + fi + done +} + +CURL_ARGS="-s --connect-timeout ${CONNECT_TIMEOUT}" +fetch() { + # shellcheck disable=SC2086 + curl -f ${CURL_ARGS} "$@" \ + || { echo "error fetching ${*}" >&2; false; } +} + +get_inverter_info() { + fetch "${INVERTER_BASE_URL}/solar_api/v1/GetInverterInfo.cgi" \ + | recode html..ascii +} + +get_power_flow_realtime_data() { + fetch "${INVERTER_BASE_URL}/solar_api/v1/GetPowerFlowRealtimeData.fcgi" + #echo "${GetPowerFlowRealtimeData} +} + +# Run the command and arguments passed as arguments to this method, and cache +# the response. The first argument is a timeout (in minutes) after which the +# cache is ignored and a new request is attempted. If the request fails, the +# cache is used. If the timeout is 0, the request is always attempted, using +# the cache as a backup on failure. +cached() { + timeout="${1}" + shift + fn="${1}" + shift + # shellcheck disable=SC2124 + args="${@}" + + # shellcheck disable=SC2039 + api_data='' + # shellcheck disable=SC2039 + cachefile="${MUNIN_PLUGSTATE}/$(basename "${0}").${fn}.cache.json" + if [ -n "$(find "${cachefile}" -mmin "-${timeout}" 2>/dev/null)" ]; then + api_data=$(cat "${cachefile}") + else + # shellcheck disable=SC2086 + api_data="$("${fn}" ${args} \ + || true)" + + if [ -n "${api_data}" ]; then + echo "${api_data}" > "${cachefile}" + else + api_data=$(cat "${cachefile}") + fi + fi + echo "${api_data}" +} + +config() { + if test -n "${HOST_NAME}"; then + echo "host_name ${HOST_NAME}" + fi + # graph_period is not a shell variable + cat <<'EOF' +graph_title Solar Inverter Output +graph_info Power generated from solar inverters +graph_total Total output +graph_category sensors +graph_vlabel Average output [W] +graph_args -l 0 --base 1000 +EOF +cached 720 get_inverter_info | jq -r '.Body.Data + | to_entries[] + | @text " +inverter\(.key).label \(.value.CustomName) +inverter\(.key).info Power generated by the solar array (total size \(.value.PVPower / 1000) kW) connected to inverter \(.value.CustomName) (ID: \(.value.UniqueID)) +inverter\(.key).cdef inverter\(.key),3600,* +inverter\(.key).type DERIVE +inverter\(.key).min 0 +inverter\(.key).max \(.value.PVPower) +inverter\(.key).draw AREASTACK +"' +} + +get_data() { +cached 0 get_power_flow_realtime_data | jq -r '.Body.Data.Inverters + | to_entries[] + | @text " +inverter\(.key).value \(.value.E_Year | round) +inverter\(.key).extinfo Immediate output: \(.value.P) W; Daily total: \(.value.E_Day | round) Wh; Yearly total: \(.value.E_Year / 1000 | round) kWh +"' +} + +main () { + check_deps + + case ${1:-} in + config) + config + if [ "${MUNIN_CAP_DIRTYCONFIG:-0}" = "1" ]; then + get_data + fi + ;; + *) + get_data + ;; + esac +} + +main "${1:-}"