mirror of
https://github.com/munin-monitoring/contrib.git
synced 2025-07-21 18:41:03 +00:00
"round" is part of jq's math support. Some distros (rhel8) don't enable it. Fortunately round can be emulated with "floor" which is always present in modern jq.
290 lines
6.9 KiB
Bash
Executable file
290 lines
6.9 KiB
Bash
Executable file
#!/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 and Fronius Symo.
|
|
|
|
=head1 AUTHOR
|
|
|
|
Olivier Mehani
|
|
|
|
Copyright (C) 2020 Olivier Mehani <shtrom+munin@ssji.net>
|
|
|
|
=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)"
|
|
exit 0
|
|
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 'def roundit: . + 0.5 | floor;
|
|
.Body.Data.Inverters
|
|
| to_entries[]
|
|
| @text "
|
|
inverter\(.key).value \(.value.E_Year | roundit)
|
|
inverter\(.key).extinfo Immediate output: \(.value.P) W; Daily total: \(.value.E_Day | roundit) Wh; Yearly total: \(.value.E_Year / 1000 | roundit) kWh
|
|
"'
|
|
}
|
|
|
|
main () {
|
|
check_deps
|
|
|
|
case ${1:-} in
|
|
config)
|
|
config
|
|
if [ "${MUNIN_CAP_DIRTYCONFIG:-0}" = "1" ]; then
|
|
get_data
|
|
fi
|
|
;;
|
|
*)
|
|
get_data
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "${1:-}"
|