From fc7f467539851adde52ab2f96305427ad771c194 Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Mon, 23 Oct 2023 13:37:00 +0200 Subject: [PATCH 01/24] Create main_versioning.yml --- .github/workflows/main_versioning.yml | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/main_versioning.yml diff --git a/.github/workflows/main_versioning.yml b/.github/workflows/main_versioning.yml new file mode 100644 index 0000000..5b5cae8 --- /dev/null +++ b/.github/workflows/main_versioning.yml @@ -0,0 +1,38 @@ +name: Main Versioning + +# This workflow triggers when a pull request to the 'main' branch is closed +on: + pull_request: + types: [closed] + branches: + - main + +jobs: + update_version: + # Ensure this job only runs if the PR was merged, not just closed without merging + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Update VERSION in main + run: | + # Remove the -DEV suffix from the version number + NEW_VERSION=$(sed -n 's/VERSION=//p' scripts/controller.sh | sed 's/-DEV//') + sed -i "s/VERSION=.*/VERSION=$NEW_VERSION/" scripts/controller.sh + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git commit -am "Update version to $NEW_VERSION" + git push origin main + + - name: Merge changes back to dev and append -DEV + run: | + git checkout dev + git merge main --no-ff -m "Merge main changes back to dev" + # Append -DEV back to the version number for the dev branch + NEW_DEV_VERSION="$NEW_VERSION-DEV" + sed -i "s/VERSION=.*/VERSION=$NEW_DEV_VERSION/" scripts/controller.sh + git commit -am "Update version to $NEW_DEV_VERSION in dev" + git push origin dev From e08056a5dcbee3b00e7663fbe317f84f9c03ebbd Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Mon, 23 Oct 2023 20:32:47 +0200 Subject: [PATCH 02/24] Update main_versioning.yml fix missing " --- .github/workflows/main_versioning.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main_versioning.yml b/.github/workflows/main_versioning.yml index 5b5cae8..dc54a01 100644 --- a/.github/workflows/main_versioning.yml +++ b/.github/workflows/main_versioning.yml @@ -21,10 +21,10 @@ jobs: run: | # Remove the -DEV suffix from the version number NEW_VERSION=$(sed -n 's/VERSION=//p' scripts/controller.sh | sed 's/-DEV//') - sed -i "s/VERSION=.*/VERSION=$NEW_VERSION/" scripts/controller.sh + sed -i "s/VERSION=.*/VERSION=${NEW_VERSION}/" scripts/controller.sh git config --local user.email "action@github.com" git config --local user.name "GitHub Action" - git commit -am "Update version to $NEW_VERSION" + git commit -am "Update version to ${NEW_VERSION}" git push origin main - name: Merge changes back to dev and append -DEV @@ -32,7 +32,7 @@ jobs: git checkout dev git merge main --no-ff -m "Merge main changes back to dev" # Append -DEV back to the version number for the dev branch - NEW_DEV_VERSION="$NEW_VERSION-DEV" - sed -i "s/VERSION=.*/VERSION=$NEW_DEV_VERSION/" scripts/controller.sh - git commit -am "Update version to $NEW_DEV_VERSION in dev" + NEW_DEV_VERSION="${NEW_VERSION}-DEV" + sed -i "s/VERSION=.*/VERSION=${NEW_DEV_VERSION}/" scripts/controller.sh + git commit -am "Update version to ${NEW_DEV_VERSION} in dev" git push origin dev From b9abf287e63d42ac11c998ddec335c995d27724b Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Mon, 23 Oct 2023 20:49:50 +0200 Subject: [PATCH 03/24] Update main_versioning.yml --- .github/workflows/main_versioning.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_versioning.yml b/.github/workflows/main_versioning.yml index dc54a01..32fb500 100644 --- a/.github/workflows/main_versioning.yml +++ b/.github/workflows/main_versioning.yml @@ -33,6 +33,6 @@ jobs: git merge main --no-ff -m "Merge main changes back to dev" # Append -DEV back to the version number for the dev branch NEW_DEV_VERSION="${NEW_VERSION}-DEV" - sed -i "s/VERSION=.*/VERSION=${NEW_DEV_VERSION}/" scripts/controller.sh + sed -i "s/^VERSION=.*$/VERSION=\"${NEW_VERSION}\"/" scripts/controller.sh git commit -am "Update version to ${NEW_DEV_VERSION} in dev" git push origin dev From 746c8f8275548cb8f3916cf8c04ba3afc148db84 Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Mon, 23 Oct 2023 20:59:22 +0200 Subject: [PATCH 04/24] Update main_versioning.yml --- .github/workflows/main_versioning.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main_versioning.yml b/.github/workflows/main_versioning.yml index 32fb500..12053ce 100644 --- a/.github/workflows/main_versioning.yml +++ b/.github/workflows/main_versioning.yml @@ -20,8 +20,8 @@ jobs: - name: Update VERSION in main run: | # Remove the -DEV suffix from the version number - NEW_VERSION=$(sed -n 's/VERSION=//p' scripts/controller.sh | sed 's/-DEV//') - sed -i "s/VERSION=.*/VERSION=${NEW_VERSION}/" scripts/controller.sh + NEW_VERSION=$(sed -n 's/^VERSION=\"\(.*\)-DEV\"$/\1/p' scripts/controller.sh) + sed -i "s/^VERSION=\".*\"$/VERSION=\"${NEW_VERSION}\"/" scripts/controller.sh git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git commit -am "Update version to ${NEW_VERSION}" @@ -33,6 +33,6 @@ jobs: git merge main --no-ff -m "Merge main changes back to dev" # Append -DEV back to the version number for the dev branch NEW_DEV_VERSION="${NEW_VERSION}-DEV" - sed -i "s/^VERSION=.*$/VERSION=\"${NEW_VERSION}\"/" scripts/controller.sh + sed -i "s/^VERSION=\".*\"$/VERSION=\"${NEW_DEV_VERSION}\"/" scripts/controller.sh git commit -am "Update version to ${NEW_DEV_VERSION} in dev" git push origin dev From 4e7abab312a961948d06c665c24a2c9d9f5257d1 Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Mon, 23 Oct 2023 23:49:15 +0200 Subject: [PATCH 05/24] Update controller.sh add version number add bash version info add skip info for not activated options fix Venus OS run failure remove bc and some stuff i can't remember --- scripts/controller.sh | 96 +++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/scripts/controller.sh b/scripts/controller.sh index dea098d..f887516 100755 --- a/scripts/controller.sh +++ b/scripts/controller.sh @@ -28,6 +28,8 @@ License=$( EOLICENSE ) +VERSION="2.3.0-DEV" + set -e if [ -z "$LANG" ]; then @@ -132,7 +134,7 @@ if [ -f "$DIR/config.txt" ]; then # Include the configuration file source "$DIR/config.txt" else - log_info "E: The file $DIR/config.txt was not found! Configure the existing sample.config.txt file and then save it as config.txt in the same directory." false + echo "E: The file $DIR/config.txt was not found! Configure the existing sample.config.txt file and then save it as config.txt in the same directory." false exit 127 fi @@ -237,7 +239,6 @@ fi unset num_tools_missing - ####################################### ### Begin of the functions... ### ####################################### @@ -301,7 +302,7 @@ parse_and_validate_config() { local file="$1" local errors="" - rotating_spinner & # Start the spinner in the background + rotating_spinner & # Start the spinner in the background local spinner_pid=$! # Get the PID of the spinner # Step 1: Parse @@ -315,7 +316,7 @@ parse_and_validate_config() { # Set the value in the associative array config_values["$key"]="$value" - done < "$file" + done <"$file" # Step 2: Validation for var_name in "${!valid_vars[@]}"; do @@ -479,14 +480,14 @@ download_entsoe_prices() { exit_with_cleanup 1 fi - if [ -n "$DEBUG" ]; then log_info "D: No delay of download of entsoe data since DEBUG variable set." >&2 "D: Entsoe file '$file' with price data downloaded" >&2; fi + if [ -n "$DEBUG" ]; then log_info "D: No delay of download of entsoe data since DEBUG variable set." "D: Entsoe file '$file' with price data downloaded" >&2 >&2; fi if [ ! -s "$file" ]; then log_info "E: Entsoe file '$file' is empty, please check your entsoe API Key." exit_with_cleanup 1 fi - if [ -n "$DEBUG" ]; then log_info "D: No delay of download of entsoe data since DEBUG variable set." >&2 "D: Entsoe file '$file' with price data downloaded"; fi + if [ -n "$DEBUG" ]; then log_info "D: No delay of download of entsoe data since DEBUG variable set." "D: Entsoe file '$file' with price data downloaded" >&2; fi awk ' # Capture content inside the tag @@ -545,13 +546,13 @@ download_entsoe_prices() { log_info "E: No prices found in the tomorrow XML data." } ' "$file" - + if [ -f "$output_file" ]; then - sort -g "$output_file" > "${output_file%.*}_sorted.${output_file##*.}" + sort -g "$output_file" >"${output_file%.*}_sorted.${output_file##*.}" timestamp=$(TZ=$TZ date +%d) - echo "date_now_day: $timestamp" >> "$output_file" + echo "date_now_day: $timestamp" >>"$output_file" fi - + # Check if tomorrow file contains next day prices if [ "$include_second_day" = 1 ] && grep -q "PT60M" "$file" && [ "$(wc -l <"$output_file")" -gt 3 ]; then cat $file10 >$file8 @@ -616,15 +617,15 @@ get_awattar_prices() { } get_tibber_prices() { - current_price=$(sed -n "${now_linenumber}s/.*\"${tibber_prices}\":\([^,]*\),.*/\1/p" "$file15") - lowest_price=$(sed -n "1s/.*\"${tibber_prices}\":\([^,]*\),.*/\1/p" "$file12") - second_lowest_price=$(sed -n "2s/.*\"${tibber_prices}\":\([^,]*\),.*/\1/p" "$file12") - third_lowest_price=$(sed -n "3s/.*\"${tibber_prices}\":\([^,]*\),.*/\1/p" "$file12") - fourth_lowest_price=$(sed -n "4s/.*\"${tibber_prices}\":\([^,]*\),.*/\1/p" "$file12") - fifth_lowest_price=$(sed -n "5s/.*\"${tibber_prices}\":\([^,]*\),.*/\1/p" "$file12") - sixth_lowest_price=$(sed -n "6s/.*\"${tibber_prices}\":\([^,]*\),.*/\1/p" "$file12") - highest_price=$(sed -n "s/.*\"${tibber_prices}\":\([^,]*\),.*/\1/p" "$file12" | awk 'BEGIN {max = 0} {if ($1 > max) max = $1} END {print max}') - average_price=$(sed -n "s/.*\"${tibber_prices}\":\([^,]*\),.*/\1/p" "$file12" | awk '{sum += $1} END {print sum/NR}') + current_price=$(sed -n "${now_linenumber}s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file15") + lowest_price=$(sed -n "1s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") + second_lowest_price=$(sed -n "2s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") + third_lowest_price=$(sed -n "3s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") + fourth_lowest_price=$(sed -n "4s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") + fifth_lowest_price=$(sed -n "5s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") + sixth_lowest_price=$(sed -n "6s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") + highest_price=$(sed -n "s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12" | awk 'BEGIN {max = 0} {if ($1 > max) max = $1} END {print max}') + average_price=$(sed -n "s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12" | awk '{sum += $1} END {print sum/NR}') } get_current_entsoe_day() { current_entsoe_day=$(sed -n 25p "$file10" | grep -Eo '[0-9]+'); } @@ -712,10 +713,12 @@ evaluate_conditions() { for condition in "${!conditions_ref[@]}"; do if [ -n "$DEBUG" ]; then - result="( ${descriptions_ref[$condition]} ) evaluates to $([ "${conditions_ref[$condition]}" -eq 1 ] && echo true || echo false)" + description_value="${descriptions_ref[$condition]}" + condition_evaluation=$([ "${conditions_ref[$condition]}" -eq 1 ] && echo true || echo false) + result="($description_value) evaluates to $condition_evaluation" log_info "D: condition_evaluation [ $result ]." >&2 fi - + if ((conditions_ref[$condition])) && [[ $condition_met -eq 0 ]]; then execute_ref=1 condition_met_ref="$condition" @@ -747,7 +750,10 @@ is_charging_economical() { if [ -n "$DEBUG" ]; then log_info "D: is_charging_economical [ $is_economical - $([ "$is_economical" -eq 1 ] && echo "false" || echo "true") ]." >&2 - log_info "D: if [ reference_price ($(millicentToEuro $reference_price)) > total_cost ($(millicentToEuro $total_cost)) ] result is $([ "$is_economical" -eq 1 ] && echo "false" || echo "true")." >&2 + reference_price_euro=$(millicentToEuro $reference_price) + total_cost_euro=$(millicentToEuro $total_cost) + is_economical_str=$([ "$is_economical" -eq 1 ] && echo "false" || echo "true") + log_info "D: if [ reference_price $reference_price_euro > total_cost $total_cost_euro ] result is $is_economical_str." >&2 fi return $is_economical @@ -872,12 +878,8 @@ euroToMillicent() { # Replace each comma with a period, fixme if this is wrong euro=$(echo "$euro" | sed 's/,/./g') - if which bc >/dev/null 2>&1; then - # Using bc to multiply the euro number and convert it to an integer - v=$(echo "scale=0; $euro * 10^$potency / 1" | bc) - else - v=$(awk "BEGIN {print int($euro * (10 ^ $potency))}") - fi + # v=$(awk "BEGIN {print int($euro * (10 ^ $potency))}") + v=$(awk -v euro="$euro" -v potency="$potency" 'BEGIN {printf "%.0f", euro * (10 ^ potency)}') if [ -z "$v" ]; then log_info "E: Could not translate '$euro' to an integer." @@ -898,26 +900,28 @@ euroToMillicent_test() { log_info() { local msg="$1" - local prefix=$(echo "$msg" | head -n 1 | cut -d' ' -f1) # Extract the first word from the first line - local color="\033[1m" # Default color - local writeToLog=true # Default is true + local prefix=$(echo "$msg" | head -n 1 | cut -d' ' -f1) # Extract the first word from the first line + local color="\033[1m" # Default color + local writeToLog=true # Default is true case "$prefix" in - "E:") color="\033[1;31m" ;; # Bright Red - "D:") color="\033[1;34m" # Bright Blue - writeToLog=false ;; # Default to not log debug messages - "W:") color="\033[1;33m" ;; # Bright Yellow - "I:") color="\033[1;32m" ;; # Bright Green + "E:") color="\033[1;31m" ;; # Bright Red + "D:") + color="\033[1;34m" # Bright Blue + writeToLog=false + ;; # Default to not log debug messages + "W:") color="\033[1;33m" ;; # Bright Yellow + "I:") color="\033[1;32m" ;; # Bright Green esac - writeToLog="${2:-$writeToLog}" # Override default if second parameter is provided + writeToLog="${2:-$writeToLog}" # Override default if second parameter is provided # Print to console with color codes printf "${color}%b\033[0m\n" "$msg" # If we should write to the log, write without color codes if [ "$writeToLog" == "true" ]; then - echo -e "$msg" | sed 's/\x1b\[[0-9;]*m//g' >> "$LOG_FILE" + echo -e "$msg" | sed 's/\x1b\[[0-9;]*m//g' >>"$LOG_FILE" fi } @@ -935,9 +939,12 @@ exit_with_cleanup() { echo >>"$LOG_FILE" +log_info "I: Bash Version: $(bash --version | head -n 1)" +log_info "I: Spotmarket-Switcher - Version $VERSION" + parse_and_validate_config "$DIR/config.txt" # if [ $? -eq 1 ]; then - # Handle error +# Handle error # fi # An independent segment to test the conversion of floats to integers @@ -1087,6 +1094,8 @@ if ((use_solarweather_api_to_abort == 1)); then log_info "E: File '$file3' is empty, please check your API Key if download is still not possible tomorrow." fi find "$file3" -size 0 -delete # FIXME - looks wrong and complicated - simple RM included in prior if clause? +else + log_info "W: skip Solarweather. not activated" fi charging_condition_met="" @@ -1173,15 +1182,21 @@ if ((execute_charging == 1 && use_victron_charger == 1)); then fi elif ((execute_charging != 1 && use_victron_charger == 1)); then manage_charging "off" "Charging was not executed." +else + log_info "W: skip Victron Charger. not activated" fi # Execute Fritz DECT on command if ((use_fritz_dect_sockets == 1)); then manage_fritz_sockets +else + log_info "W: skip Fritz DECT. not activated" fi if ((use_shelly_wlan_sockets == 1)); then manage_shelly_sockets +else + log_info "W: skip Shelly Api. not activated" fi echo >>"$LOG_FILE" @@ -1189,6 +1204,7 @@ echo >>"$LOG_FILE" # Rotating log files if [ -f "$LOG_FILE" ]; then if [ "$(du -k "$LOG_FILE" | awk '{print $1}')" -gt "$LOG_MAX_SIZE" ]; then + log_info "I: Rotating log files" mv "$LOG_FILE" "${LOG_FILE}.$(date +%Y%m%d%H%M%S)" touch "$LOG_FILE" find . -maxdepth 1 -name "${LOG_FILE}*" -type f -exec ls -1t {} + | @@ -1199,5 +1215,5 @@ if [ -f "$LOG_FILE" ]; then fi if [ -n "$DEBUG" ]; then - log_info "D: [ OK ]" >&2 + log_info "D: \[ OK \]" >&2 fi From 0cd102399f12f5c46f15a65c6f51d82ed535de28 Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Mon, 23 Oct 2023 23:52:03 +0200 Subject: [PATCH 06/24] Update run Fix Venus OS failure --- scripts/run | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/scripts/run b/scripts/run index 82a2441..23cb308 100755 --- a/scripts/run +++ b/scripts/run @@ -1,6 +1,7 @@ -#!/bin/sh +#!/bin/bash -License=$(cat < "$rc_local_file" + echo "#!/bin/sh" >"$rc_local_file" chmod +x "$rc_local_file" - $code fi # Check if the code is already in rc.local, if not, add it if ! grep -qF "$code" "$rc_local_file"; then - echo "$code" >> "$rc_local_file" - $code + { + echo '(crontab -l | grep -Fxq "0 * * * * /data/etc/Spotmarket-Switcher/controller.sh") || (crontab -l; echo "0 * * * * /data/etc/Spotmarket-Switcher/controller.sh")' + (crontab -l | grep -Fxq "0 * * * * /data/etc/Spotmarket-Switcher/controller.sh") || ( + crontab -l + echo "0 * * * * /data/etc/Spotmarket-Switcher/controller.sh" + ) | crontab - + } | tee -a "$rc_local_file" fi # Display a success message in DEBUG mode From 4315d7441cbb2b3cd323ff2b6387736b6fb5fb98 Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Mon, 23 Oct 2023 23:54:52 +0200 Subject: [PATCH 07/24] Update victron-venus-os-install.sh fix Venus OS failure fix crontab not found --- victron-venus-os-install.sh | 198 +++++++++++++++++++----------------- 1 file changed, 103 insertions(+), 95 deletions(-) diff --git a/victron-venus-os-install.sh b/victron-venus-os-install.sh index 0b70cc0..de4caa1 100755 --- a/victron-venus-os-install.sh +++ b/victron-venus-os-install.sh @@ -1,7 +1,7 @@ -#!/bin/sh +#!/bin/bash License=$( - cat </dev/null; then - missing="$missing $tool" - fi + if ! which "$tool" >/dev/null; then + missing="$missing $tool" + fi done if [ -n "$missing" ]; then - e_error "E: Install the following tools prior to running this install script or the installed scripts: $missing" - exit 1 + e_error "E: Install the following tools prior to running this install script or the installed scripts: $missing" + exit 1 fi for tool in wget curl; do - if ! which "$tool" >/dev/null; then - missing="$missing $tool" - fi + if ! which "$tool" >/dev/null; then + missing="$missing $tool" + fi done if [ -n "$missing" ]; then - e_note "W: Install the following tools prior to the execution of the installed scripts: $missing." - e_note " Try running 'opkg install $missing'." - echo - e_note " Now continuing with the installation, which will be fine per se, but you as the user are responsible to get those dependencies installed to prevent the control script from failing. Drop an issue at https://github.com/christian1980nrw/Spotmarket-Switcher/issues if this package shall somehow prepare you better." - echo + e_note "W: Install the following tools prior to the execution of the installed scripts: $missing." + e_note " Try running 'opkg install $missing'." + echo + e_note " Now continuing with the installation, which will be fine per se, but you as the user are responsible to get those dependencies installed to prevent the control script from failing. Drop an issue at https://github.com/christian1980nrw/Spotmarket-Switcher/issues if this package shall somehow prepare you better." + echo fi # DESTDIR is optionally set as an environment variable. if [ -n "$DESTDIR" ] && [ "/" != "$DESTDIR" ]; then - e_note "W: The environment variable DESTDIR is set to the value '$DESTDIR' that is different from '/', the root directory." - e_note " This is meant to support testing and packaging, not for a true installation." - e_underline " If you are using Victron Venus OS, the correct installation directory should be '/'." - e_note " No harm is expected to be caused, but it's recommended to install directly to '/' for a standard installation." - e_note " You can cancel now with CTRL-C if this is not what you intended." - sleep 5 - e_underline "I: Will now continue. You can still interrupt at any time." - echo + e_note "W: The environment variable DESTDIR is set to the value '$DESTDIR' that is different from '/', the root directory." + e_note " This is meant to support testing and packaging, not for a true installation." + e_underline " If you are using Victron Venus OS, the correct installation directory should be '/'." + e_note " No harm is expected to be caused, but it's recommended to install directly to '/' for a standard installation." + e_note " You can cancel now with CTRL-C if this is not what you intended." + sleep 5 + e_underline "I: Will now continue. You can still interrupt at any time." + echo else - ln -s /data/etc/Spotmarket-Switcher/service /service/Spotmarket-Switcher - (crontab -l | grep -Fxq "0 * * * * /data/etc/Spotmarket-Switcher/controller.sh") || ( - crontab -l - echo "0 * * * * /data/etc/Spotmarket-Switcher/controller.sh" - ) | crontab - + ln -s /data/etc/Spotmarket-Switcher/service /service/Spotmarket-Switcher + (crontab -l | grep -Fxq "0 * * * * /data/etc/Spotmarket-Switcher/controller.sh") || ( + crontab -l + echo "0 * * * * /data/etc/Spotmarket-Switcher/controller.sh" + ) | crontab - fi if ! mkdir -p "$DESTDIR"/data/etc/Spotmarket-Switcher/service; then - e_error "E: Could not create service directory '$DESTDIR/data/etc/Spotmarket-Switcher/service'." - exit 1 + e_error "E: Could not create service directory '$DESTDIR/data/etc/Spotmarket-Switcher/service'." + exit 1 fi downloadToDest() { - url="$1" - dest="$2" - - echo "I: Downloading '$(basename "$url")'" - if ! wget --no-verbose --continue --no-directories --show-progress -O "$dest" "$url"; then - e_error "E: Download of '$(basename "$url")' failed." - return 1 - fi - chmod +x "$dest" + url="$1" + dest="$2" + + echo "I: Downloading '$(basename "$url")'" + if ! wget --no-verbose --continue --no-directories --show-progress -O "$dest" "$url"; then + e_error "E: Download of '$(basename "$url")' failed." + return 1 + fi + chmod +x "$dest" } download_file_if_missing() { - local file_path="$1" - local dest_path="$2" - local file_url="$3" - - if [ -x "$file_path" ]; then - cp "$file_path" "$dest_path" - else - if [ -n "$DEBUG" ]; then - echo "D: ls \$SRCDIR" - ls "$SRCDIR" || { - echo "D: pwd: $(pwd)" - ls - } - fi - e_note "I: Downloading '$(basename "$file_path")' from github repository - '$BRANCH' branch" - downloadToDest "$file_url" "$dest_path" - fi + local file_path="$1" + local dest_path="$2" + local file_url="$3" + + if [ -x "$file_path" ]; then + cp "$file_path" "$dest_path" + else + if [ -n "$DEBUG" ]; then + echo "D: ls \$SRCDIR" + ls "$SRCDIR" || { + echo "D: pwd: $(pwd)" + ls + } + fi + e_note "I: Downloading '$(basename "$file_path")' from github repository - '$BRANCH' branch" + downloadToDest "$file_url" "$dest_path" + fi } if [ -z "$SRCDIR" ]; then - SRCDIR=scripts + SRCDIR=scripts fi if [ -z "$branch" ]; then - BRANCH=main + BRANCH=main fi download_file_if_missing "$SRCDIR/controller.sh" "$DESTDIR/data/etc/Spotmarket-Switcher/controller.sh" https://raw.githubusercontent.com/christian1980nrw/Spotmarket-Switcher/"$BRANCH"/scripts/controller.sh @@ -227,31 +235,31 @@ cp -n "$DESTDIR/data/etc/Spotmarket-Switcher/sample.config.txt" "$DESTDIR/data/e # $DESTDIR is always an absolut path if [ ! -d "$DESTDIR"/service ]; then - if [ -n "$DESTDIR" ] && [ "/" != "$DESTDIR" ]; then - e_note "I: The '$DESTDIR/service' directory is not existing, as expected because of the custom DESTDIR setting." - e_note " Skipping creation of symbolic link to the Sportmarket-Switcher to register this service." - else - e_note "W: The '$DESTDIR/service' directory is not existing." - e_note " Not installing a symbolic link to the Sportmarket-Switcher to register this service." - e_note " Check on https://github.com/christian1980nrw/Spotmarket-Switcher/issues if that has already been reported." - fi + if [ -n "$DESTDIR" ] && [ "/" != "$DESTDIR" ]; then + e_note "I: The '$DESTDIR/service' directory is not existing, as expected because of the custom DESTDIR setting." + e_note " Skipping creation of symbolic link to the Sportmarket-Switcher to register this service." + else + e_note "W: The '$DESTDIR/service' directory is not existing." + e_note " Not installing a symbolic link to the Sportmarket-Switcher to register this service." + e_note " Check on https://github.com/christian1980nrw/Spotmarket-Switcher/issues if that has already been reported." + fi else - if [ ! -L "$DESTDIR"/service/Spotmarket-Switcher ]; then - ln -s "$DESTDIR"/data/etc/Spotmarket-Switcher/service "$DESTDIR"/service/Spotmarket-Switcher - fi + if [ ! -L "$DESTDIR"/service/Spotmarket-Switcher ]; then + ln -s "$DESTDIR"/data/etc/Spotmarket-Switcher/service "$DESTDIR"/service/Spotmarket-Switcher + fi fi if [ -e "$DESTDIR"/data/rc.local ]; then - if grep -q "Spotmarket-Switcher/service /service/Spotmarket-Switcher" "$DESTDIR"/data/rc.local; then - e_note "I: Spotmarket-Switcher/service is already known to rc.local boot script - not added again." - else - e_note "I: Adding link to Spotmarket-Switcher/service to rc.local boot script." - sed -i '1s|^|ln -s /data/etc/Spotmarket-Switcher/service /service/Spotmarket-Switcher\n|' /data/rc.local - fi + if grep -q "Spotmarket-Switcher/service /service/Spotmarket-Switcher" "$DESTDIR"/data/rc.local; then + e_note "I: Spotmarket-Switcher/service is already known to rc.local boot script - not added again." + else + e_note "I: Adding link to Spotmarket-Switcher/service to rc.local boot script." + sed -i '1s|^|ln -s /data/etc/Spotmarket-Switcher/service /service/Spotmarket-Switcher\n|' /data/rc.local + fi else - e_note "I: Creating new data/rc.local boot script" - echo "ln -s /data/etc/Spotmarket-Switcher/service /service/Spotmarket-Switcher" >"$DESTDIR"/data/rc.local - chmod +x "$DESTDIR"/data/rc.local + e_note "I: Creating new data/rc.local boot script" + echo "ln -s /data/etc/Spotmarket-Switcher/service /service/Spotmarket-Switcher" >"$DESTDIR"/data/rc.local + chmod +x "$DESTDIR"/data/rc.local fi echo @@ -266,6 +274,6 @@ echo e_note "Note: This installation will survive a Venus OS firmware update." echo if [ -n "$missing" ]; then - e_note "Note: Remember to install these missing executables: $missing" - echo + e_note "Note: Remember to install these missing executables: $missing" + echo fi From e20cb32e7baf466dc9359502252f5654ac2bd4e8 Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Mon, 23 Oct 2023 23:57:23 +0200 Subject: [PATCH 08/24] Update venus.yml fix --- .github/workflows/venus.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/venus.yml b/.github/workflows/venus.yml index 6f63c93..4b8aee5 100644 --- a/.github/workflows/venus.yml +++ b/.github/workflows/venus.yml @@ -19,7 +19,7 @@ jobs: if which apt > /dev/null then apt update - apt -y install wget curl + apt -y install wget curl cron elif which opkg > /dev/null then opkg install wget @@ -27,19 +27,24 @@ jobs: else echo "W: Tests limited because of non-avail of wget and curl" fi + - name: Set script permissions + run: chmod +x ./victron-venus-os-install.sh + env: + DEBUG: 1 - name: Execute Installation under Venus OS run: | echo pwd pwd echo ls ls - echo victron-venus-install.sh + echo victron-venus-os-install.sh if ./victron-venus-os-install.sh ; then echo "[OK]" else echo "[FAIL]" pwd - find . | head -n 30 + HEAD_PATH=$(which head) + find . | $HEAD_PATH -n 30 exit 1 fi env: From 9be9b544585e696d5d6ec3a4724abb0bc70b55d5 Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Tue, 24 Oct 2023 00:01:24 +0200 Subject: [PATCH 09/24] Update victron-venus-os-install.sh fix the right code :) --- victron-venus-os-install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/victron-venus-os-install.sh b/victron-venus-os-install.sh index de4caa1..a6aca1d 100755 --- a/victron-venus-os-install.sh +++ b/victron-venus-os-install.sh @@ -28,6 +28,7 @@ EOLICENSE ) set -e +set -x if [ -z "$LANG" ]; then export LANG=C @@ -223,7 +224,7 @@ if [ -z "$SRCDIR" ]; then SRCDIR=scripts fi if [ -z "$branch" ]; then - BRANCH=main + BRANCH=dev fi download_file_if_missing "$SRCDIR/controller.sh" "$DESTDIR/data/etc/Spotmarket-Switcher/controller.sh" https://raw.githubusercontent.com/christian1980nrw/Spotmarket-Switcher/"$BRANCH"/scripts/controller.sh From 44464e61a9c4296ddffe45ad3e60e9273adca9d0 Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Tue, 24 Oct 2023 00:07:47 +0200 Subject: [PATCH 10/24] Create dev_versioning.yml add versioning --- .github/workflows/dev_versioning.yml | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/dev_versioning.yml diff --git a/.github/workflows/dev_versioning.yml b/.github/workflows/dev_versioning.yml new file mode 100644 index 0000000..8736e94 --- /dev/null +++ b/.github/workflows/dev_versioning.yml @@ -0,0 +1,44 @@ +name: DEV Versioning + +# This workflow triggers on every push to the 'dev' branch +on: + push: + branches: + - dev + +jobs: + update_version: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Determine size of changes + id: changes + run: | + # Get the number of lines changed since the last commit + LINES_CHANGED=$(git diff HEAD~1 --stat | tail -n 1 | awk '{print $4+$6}') + echo "LINES_CHANGED=$LINES_CHANGED" + # Check if the change is "big" (more than 100 lines in this example) + if [[ "$LINES_CHANGED" -gt 100 ]]; then + echo "::set-output name=big_change::true" + else + echo "::set-output name=big_change::false" + fi + + - name: Update VERSION + run: | + # If the change is "big", increment the minor version. Otherwise, increment the patch version. + CURRENT_VERSION=$(sed -n 's/^VERSION=\"\(.*\)-DEV\"$/\1/p' scripts/controller.sh) + if [[ "${{ steps.changes.outputs.big_change }}" == "true" ]]; then + NEW_VERSION=$(echo "$CURRENT_VERSION" | awk -F. -v OFS=. '{$2=$2+1; $3=0;print}') + else + NEW_VERSION=$(echo "$CURRENT_VERSION" | awk -F. -v OFS=. '{$3=$3+1;print}') + fi + # Append -DEV to the version number + sed -i "s/^VERSION=\".*\"$/VERSION=\"$NEW_VERSION-DEV\"/" scripts/controller.sh + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git commit -am "Update version to $NEW_VERSION-DEV" + git push From a2f6ac6d92033941b821167a53998269c101fad8 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 23 Oct 2023 22:08:07 +0000 Subject: [PATCH 11/24] Update version to 2.3.1-DEV --- scripts/controller.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/controller.sh b/scripts/controller.sh index f887516..a88450d 100755 --- a/scripts/controller.sh +++ b/scripts/controller.sh @@ -28,7 +28,7 @@ License=$( EOLICENSE ) -VERSION="2.3.0-DEV" +VERSION="2.3.1-DEV" set -e From 81b0559dfad115fd589976df24472c8253db2ee2 Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Tue, 24 Oct 2023 00:09:45 +0200 Subject: [PATCH 12/24] Update venus.yml fix --- .github/workflows/venus.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/venus.yml b/.github/workflows/venus.yml index 6f63c93..b2ff7ea 100644 --- a/.github/workflows/venus.yml +++ b/.github/workflows/venus.yml @@ -19,7 +19,7 @@ jobs: if which apt > /dev/null then apt update - apt -y install wget curl + apt -y install wget curl cron elif which opkg > /dev/null then opkg install wget @@ -27,19 +27,28 @@ jobs: else echo "W: Tests limited because of non-avail of wget and curl" fi + - name: Check crontab + run: which crontab + env: + DEBUG: 1 + - name: Set script permissions + run: chmod +x ./victron-venus-os-install.sh + env: + DEBUG: 1 - name: Execute Installation under Venus OS run: | echo pwd pwd echo ls ls - echo victron-venus-install.sh + echo victron-venus-os-install.sh if ./victron-venus-os-install.sh ; then echo "[OK]" else echo "[FAIL]" pwd - find . | head -n 30 + HEAD_PATH=$(which head) + find . | $HEAD_PATH -n 30 exit 1 fi env: From aba3e26f29c7d778508f5312a2fb08e409976c80 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 23 Oct 2023 22:14:08 +0000 Subject: [PATCH 13/24] Update version to 2.3.2-DEV --- scripts/controller.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/controller.sh b/scripts/controller.sh index a88450d..2a5c7c8 100755 --- a/scripts/controller.sh +++ b/scripts/controller.sh @@ -28,7 +28,7 @@ License=$( EOLICENSE ) -VERSION="2.3.1-DEV" +VERSION="2.3.2-DEV" set -e From fc759362bf9afcff49d5c00d78ffa3de3056d155 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 23 Oct 2023 22:14:51 +0000 Subject: [PATCH 14/24] Update version to 2.3.2 --- scripts/controller.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/controller.sh b/scripts/controller.sh index 2a5c7c8..82b6a44 100755 --- a/scripts/controller.sh +++ b/scripts/controller.sh @@ -28,7 +28,7 @@ License=$( EOLICENSE ) -VERSION="2.3.2-DEV" +VERSION="2.3.2" set -e From 407ab64394a2877f32411d9700417c80cd80d551 Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Tue, 24 Oct 2023 00:21:33 +0200 Subject: [PATCH 15/24] remove set -x I completely forgot to remove the set -x again --- victron-venus-os-install.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/victron-venus-os-install.sh b/victron-venus-os-install.sh index a6aca1d..907e3eb 100755 --- a/victron-venus-os-install.sh +++ b/victron-venus-os-install.sh @@ -28,7 +28,6 @@ EOLICENSE ) set -e -set -x if [ -z "$LANG" ]; then export LANG=C From 6a9fe6a65d98293bce7632b594e39b3ed2075cd4 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 23 Oct 2023 22:21:44 +0000 Subject: [PATCH 16/24] Update version to 2.3.3-DEV --- scripts/controller.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/controller.sh b/scripts/controller.sh index 2a5c7c8..243de36 100755 --- a/scripts/controller.sh +++ b/scripts/controller.sh @@ -28,7 +28,7 @@ License=$( EOLICENSE ) -VERSION="2.3.2-DEV" +VERSION="2.3.3-DEV" set -e From 347db3c08e945206127ef782410c7f617e1beb85 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 23 Oct 2023 22:23:24 +0000 Subject: [PATCH 17/24] Update version to ..1-DEV --- scripts/controller.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/controller.sh b/scripts/controller.sh index 82b6a44..4d2de10 100755 --- a/scripts/controller.sh +++ b/scripts/controller.sh @@ -28,7 +28,7 @@ License=$( EOLICENSE ) -VERSION="2.3.2" +VERSION="..1-DEV" set -e From a3a42832ebcb71cb54b26e4c70f9e242ff67983b Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Tue, 24 Oct 2023 00:24:36 +0200 Subject: [PATCH 18/24] Delete .github/workflows/dev_versioning.yml --- .github/workflows/dev_versioning.yml | 44 ---------------------------- 1 file changed, 44 deletions(-) delete mode 100644 .github/workflows/dev_versioning.yml diff --git a/.github/workflows/dev_versioning.yml b/.github/workflows/dev_versioning.yml deleted file mode 100644 index 8736e94..0000000 --- a/.github/workflows/dev_versioning.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: DEV Versioning - -# This workflow triggers on every push to the 'dev' branch -on: - push: - branches: - - dev - -jobs: - update_version: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Determine size of changes - id: changes - run: | - # Get the number of lines changed since the last commit - LINES_CHANGED=$(git diff HEAD~1 --stat | tail -n 1 | awk '{print $4+$6}') - echo "LINES_CHANGED=$LINES_CHANGED" - # Check if the change is "big" (more than 100 lines in this example) - if [[ "$LINES_CHANGED" -gt 100 ]]; then - echo "::set-output name=big_change::true" - else - echo "::set-output name=big_change::false" - fi - - - name: Update VERSION - run: | - # If the change is "big", increment the minor version. Otherwise, increment the patch version. - CURRENT_VERSION=$(sed -n 's/^VERSION=\"\(.*\)-DEV\"$/\1/p' scripts/controller.sh) - if [[ "${{ steps.changes.outputs.big_change }}" == "true" ]]; then - NEW_VERSION=$(echo "$CURRENT_VERSION" | awk -F. -v OFS=. '{$2=$2+1; $3=0;print}') - else - NEW_VERSION=$(echo "$CURRENT_VERSION" | awk -F. -v OFS=. '{$3=$3+1;print}') - fi - # Append -DEV to the version number - sed -i "s/^VERSION=\".*\"$/VERSION=\"$NEW_VERSION-DEV\"/" scripts/controller.sh - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git commit -am "Update version to $NEW_VERSION-DEV" - git push From 778aeabf12ebccfe0bb1ec94eb9c1f09f843ab78 Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Tue, 24 Oct 2023 00:27:10 +0200 Subject: [PATCH 19/24] Update controller.sh Version --- scripts/controller.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/controller.sh b/scripts/controller.sh index 4d2de10..82b6a44 100755 --- a/scripts/controller.sh +++ b/scripts/controller.sh @@ -28,7 +28,7 @@ License=$( EOLICENSE ) -VERSION="..1-DEV" +VERSION="2.3.2" set -e From a974e179210af85b6ccb6814ee44327de566c400 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 23 Oct 2023 22:27:24 +0000 Subject: [PATCH 20/24] Update version to ..1-DEV --- scripts/controller.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/controller.sh b/scripts/controller.sh index 82b6a44..4d2de10 100755 --- a/scripts/controller.sh +++ b/scripts/controller.sh @@ -28,7 +28,7 @@ License=$( EOLICENSE ) -VERSION="2.3.2" +VERSION="..1-DEV" set -e From d15aecb09434e4c037ec9144326181dca471719f Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Tue, 24 Oct 2023 00:28:44 +0200 Subject: [PATCH 21/24] Delete scripts/controller.sh --- scripts/controller.sh | 1219 ----------------------------------------- 1 file changed, 1219 deletions(-) delete mode 100755 scripts/controller.sh diff --git a/scripts/controller.sh b/scripts/controller.sh deleted file mode 100755 index 4d2de10..0000000 --- a/scripts/controller.sh +++ /dev/null @@ -1,1219 +0,0 @@ -#!/bin/bash - -License=$( - cat </dev/null; then - log_info "E: Please ensure the tool '$tool' is found." - num_tools_missing=$((num_tools_missing + 1)) - fi -done - -if [ $num_tools_missing -gt 0 ]; then - log_info "E: $num_tools_missing tools are missing." - exit 127 -fi - -unset num_tools_missing - -####################################### -### Begin of the functions... ### -####################################### - -declare -A valid_vars=( - ["use_fritz_dect_sockets"]="0|1" - ["fbox"]="^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" - ["user"]="string" - ["passwd"]="string" - ["sockets"]='^\(\"[^"]+\"( \"[^"]+\")*\)$' - ["use_shelly_wlan_sockets"]="0|1" - ["shelly_ips"]="^\(\".*\"\)$" - ["shellyuser"]="string" - ["shellypasswd"]="string" - ["use_victron_charger"]="0|1" - ["energy_loss_percent"]="[0-9]+(\.[0-9]+)?" - ["battery_lifecycle_costs_cent_per_kwh"]="[0-9]+(\.[0-9]+)?" - ["economic_check"]="0|1|2" - ["stop_price"]="[0-9]+(\.[0-9]+)?" - ["start_price"]="[0-9]+(\.[0-9]+)?" - ["feedin_price"]="[0-9]+(\.[0-9]+)?" - ["energy_fee"]="[0-9]+(\.[0-9]+)?" - ["abort_price"]="[0-9]+(\.[0-9]+)?" - ["use_start_stop_logic"]="0|1" - ["switchablesockets_at_start_stop"]="0|1" - ["charge_at_solar_breakeven_logic"]="0|1" - ["switchablesockets_at_solar_breakeven_logic"]="0|1" - ["charge_at_lowest_price"]="0|1" - ["switchablesockets_at_lowest_price"]="0|1" - ["charge_at_second_lowest_price"]="0|1" - ["switchablesockets_at_second_lowest_price"]="0|1" - ["charge_at_third_lowest_price"]="0|1" - ["switchablesockets_at_third_lowest_price"]="0|1" - ["charge_at_fourth_lowest_price"]="0|1" - ["switchablesockets_at_fourth_lowest_price"]="0|1" - ["charge_at_fifth_lowest_price"]="0|1" - ["switchablesockets_at_fifth_lowest_price"]="0|1" - ["charge_at_sixth_lowest_price"]="0|1" - ["switchablesockets_at_sixth_lowest_price"]="0|1" - ["TZ"]="string" - ["select_pricing_api"]="1|2|3" - ["include_second_day"]="0|1" - ["use_solarweather_api_to_abort"]="0|1" - ["abort_solar_yield_today"]="[0-9]+(\.[0-9]+)?" - ["abort_solar_yield_tomorrow"]="[0-9]+(\.[0-9]+)?" - ["abort_suntime"]="[0-9]+" - ["latitude"]="[-]?[0-9]+(\.[0-9]+)?" - ["longitude"]="[-]?[0-9]+(\.[0-9]+)?" - ["visualcrossing_api_key"]="string" - ["awattar"]="de|at" - ["in_Domain"]="string" - ["out_Domain"]="string" - ["entsoe_eu_api_security_token"]="string" - ["tibber_prices"]="energy|total|tax" - ["tibber_api_key"]="string" -) - -declare -A config_values - -parse_and_validate_config() { - local file="$1" - local errors="" - - rotating_spinner & # Start the spinner in the background - local spinner_pid=$! # Get the PID of the spinner - - # Step 1: Parse - while IFS='=' read -r key value; do - # Treat everything after a "#" as a comment and remove it - key=$(echo "$key" | cut -d'#' -f1 | tr -d ' ') - value=$(echo "$value" | awk -F'#' '{gsub(/^ *"|"$|^ *| *$/, "", $1); print $1}') - - # Only process rows with key-value pairs - [[ "$key" == "" || "$value" == "" ]] && continue - - # Set the value in the associative array - config_values["$key"]="$value" - done <"$file" - - # Step 2: Validation - for var_name in "${!valid_vars[@]}"; do - local validation_pattern=${valid_vars[$var_name]} - - # Check whether the variable was set at all - if [[ -z ${config_values[$var_name]+x} ]]; then - errors+="E: $var_name is not set.\n" - continue - fi - - # Special checking for strings, IP, and arrays - if [[ "$validation_pattern" == "string" ]]; then - # Strings can be empty or filled - continue - elif [[ "$validation_pattern" == "array" && "${config_values[$var_name]}" == "" ]]; then - continue - elif [[ "$validation_pattern" == "ip" && ! "${config_values[$var_name]}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - errors+="E: $var_name has an invalid IP address format: ${config_values[$var_name]}.\n" - continue - fi - - # Standard check against the given pattern - if ! [[ "${config_values[$var_name]}" =~ ^($validation_pattern)$ ]]; then - errors+="E: $var_name has an invalid value: ${config_values[$var_name]}.\n" - fi - done - - # Stop the spinner once the parsing is done - kill $spinner_pid &>/dev/null - - # Output errors if any were found - if [[ -n "$errors" ]]; then - echo -e "$errors" - return 1 - else - echo "Config validation passed." - return 0 - fi -} - -rotating_spinner() { - local delay=0.1 - local spinstr="|/-\\" - while true; do - local temp=${spinstr#?} - printf " [%c] Loading..." "$spinstr" - spinstr=$temp${spinstr%"$temp"} - sleep $delay - printf "\r" - done -} - -download_awattar_prices() { - local url="$1" - local file="$2" - local output_file="$3" - local sleep_time="$4" - - if [ -z "$DEBUG" ]; then - log_info "I: Please be patient. First we wait $sleep_time seconds in case the system clock is not syncronized and not to overload the API." false - sleep "$sleep_time" - fi - if ! curl "$url" >"$file"; then - log_info "E: Download of aWATTar prices from '$url' to '$file' failed." - exit_with_cleanup 1 - fi - - if ! test -f "$file"; then - log_info "E: Could not get aWATTar prices from '$url' to feed file '$file'." - exit_with_cleanup 1 - fi - - if [ -n "$DEBUG" ]; then - log_info "D: Download of file '$file' from URL '$url' successful." >&2 - fi - echo >>"$file" - awk '/data_price_hour_rel_.*_amount: / {print substr($0, index($0, ":") + 2)}' "$file" >"$output_file" - sort -g "$output_file" >"${output_file%.*}_sorted.${output_file##*.}" - timestamp=$(TZ=$TZ date +%d) - echo "date_now_day: $timestamp" >>"$output_file" - echo "date_now_day: $timestamp" >>"${output_file%.*}_sorted.${output_file##*.}" - - if [ -f "$file2" ] && [ "$(wc -l <"$file1")" = "$(wc -l <"$file2")" ]; then - rm -f "$file2" - log_info "I: File '$file2' has no tomorrow data, we have to try it again until the new prices are online." false - fi -} - -get_tibber_api() { - curl --location --request POST 'https://api.tibber.com/v1-beta/gql' \ - --header 'Content-Type: application/json' \ - --header "Authorization: Bearer $tibber_api_key" \ - --data-raw '{"query":"{viewer{homes{currentSubscription{priceInfo{current{total energy tax startsAt}today{total energy tax startsAt}tomorrow{total energy tax startsAt}}}}}}"}' | - awk '{ - gsub(/"current":/, "\n&"); - gsub(/"today":/, "\n&"); - gsub(/"tomorrow":/, "\n&"); - gsub(/"total":/, "\n&"); - print - }' -} - -download_tibber_prices() { - local url="$1" - local file="$2" - local sleep_time="$3" - - if [ -z "$DEBUG" ]; then - log_info "I: Please be patient. First we wait $sleep_time seconds in case the system clock is not syncronized and not to overload the API." false - sleep "$sleep_time" - else - log_info "D: No delay of download of Tibber data since DEBUG variable set." - fi - if ! get_tibber_api | tr -d '{}[]' >"$file"; then - log_info "E: Download of Tibber prices from '$url' to '$file' failed." - exit_with_cleanup 1 - fi - - sed -n '/"today":/,/"tomorrow":/p' "$file" | sed '$d' | sed '/"today":/d' >"$file15" - sort -t, -k1.9n $file15 >"$file16" - sed -n '/"tomorrow":/,$p' "$file" | sed '/"tomorrow":/d' >"$file17" - sort -t, -k1.9n $file17 >"$file18" - if [ "$include_second_day" = 0 ]; then - cp "$file16" "$file12" - else - grep '"total"' "$file14" | sort -t':' -k2 -n >"$file12" - fi - - timestamp=$(TZ=$TZ date +%d) - echo "date_now_day: $timestamp" >>"$file15" - echo "date_now_day: $timestamp" >>"$file17" - - if [ ! -s "$file16" ]; then - log_info "E: Tibber prices cannot be extracted to '$file16', please check your Tibber API Key." - rm "$file" - exit_with_cleanup 1 - fi -} - -download_entsoe_prices() { - local url="$1" - local file="$2" - local output_file="$3" - local sleep_time="$4" - - if [ -z "$DEBUG" ]; then - log_info "I: Please be patient. First we wait $sleep_time seconds in case the system clock is not syncronized and not to overload the API." false - sleep "$sleep_time" - else - log_info "D: No delay of download of entsoe data since DEBUG variable set." >&2 - fi - - if ! curl "$url" >"$file"; then - log_info "E: Retrieval of entsoe data from '$url' into file '$file' failed." - exit_with_cleanup 1 - fi - - if ! test -f "$file"; then - log_info "E: Could not find file '$file' with entsoe price data. Curl itself reported success." - exit_with_cleanup 1 - fi - - if [ -n "$DEBUG" ]; then log_info "D: No delay of download of entsoe data since DEBUG variable set." "D: Entsoe file '$file' with price data downloaded" >&2 >&2; fi - - if [ ! -s "$file" ]; then - log_info "E: Entsoe file '$file' is empty, please check your entsoe API Key." - exit_with_cleanup 1 - fi - - if [ -n "$DEBUG" ]; then log_info "D: No delay of download of entsoe data since DEBUG variable set." "D: Entsoe file '$file' with price data downloaded" >&2; fi - - awk ' - # Capture content inside the tag - // { - capture_period = 1 - } - /<\/Period>/ { - capture_period = 0 - } - # Ensure we are within a valid period and capture prices for one-hour resolution - capture_period && /PT60M<\/resolution>/ { - valid_period = 1 - } - valid_period && // { - gsub("", "", $0) - gsub("<\/price.amount>", "", $0) - gsub(/^[\t ]+|[\t ]+$/, "", $0) - prices = prices $0 ORS - } - valid_period && /<\/Period>/ { - exit - } - - # Capture error information inside the tag - // { - in_reason = 1 - error_message = "" - } - in_reason && // { - gsub(/|<\/code>/, "") - gsub(/^[\t ]+|[\t ]+$/, "", $0) - error_code = $0 - } - in_reason && // { - gsub(/|<\/text>/, "") - gsub(/^[\t ]+|[\t ]+$/, "", $0) - error_message = $0 - } - /<\/Reason>/ { - in_reason = 0 - } - - # At the end of processing, print out the captured prices or any error messages - END { - if (error_code == 999) { - log_info "E: Entsoe data retrieval error:", error_message - exit_with_cleanup 1 - } else if (prices != "") { - printf "%s", prices > "'"$output_file"'" - } else { - if ("'"$output_file"'" != "'"$file13"'") { - log_info "E: No prices found in the today XML data." - exit_with_cleanup 1 - } - } - log_info "E: No prices found in the tomorrow XML data." - } - ' "$file" - - if [ -f "$output_file" ]; then - sort -g "$output_file" >"${output_file%.*}_sorted.${output_file##*.}" - timestamp=$(TZ=$TZ date +%d) - echo "date_now_day: $timestamp" >>"$output_file" - fi - - # Check if tomorrow file contains next day prices - if [ "$include_second_day" = 1 ] && grep -q "PT60M" "$file" && [ "$(wc -l <"$output_file")" -gt 3 ]; then - cat $file10 >$file8 - # echo >> $file8 - if [ -f "$file13" ]; then - cat "$file13" >>"$file8" - fi - sed -i '25d 50d' "$file8" - sort -g "$file8" >"$file19" - timestamp=$(TZ=$TZ date +%d) - echo "date_now_day: $timestamp" >>"$file8" - else - cp $file11 $file19 # If no second day, copy sorted price file. - fi -} - -download_solarenergy() { - if ((use_solarweather_api_to_abort == 1)); then - delay=$((RANDOM % 15 + 1)) - if [ -z "$DEBUG" ]; then - log_info "I: Please be patient. A delay of $delay seconds will help avoid overloading the Solarweather-API." false - # Delaying a random time <=15s to reduce impact on site - download is not time-critical - sleep "$delay" - else - log_info "D: No delay of download of solarenergy data since DEBUG variable set." >&2 - fi - if ! curl "$link3" -o "$file3"; then - log_info "E: Download of solarenergy data from '$link3' failed." - exit_with_cleanup 1 - elif ! test -f "$file3"; then - log_info "E: Could not get solarenergy data, missing file '$file3'." - exit_with_cleanup 1 - fi - if [ -n "$DEBUG" ]; then - log_info "D: File3 $file3 downloaded" >&2 - fi - if ! test -f "$file3"; then - log_info "E: Could not find downloaded file '$file3' with solarenergy data." - exit_with_cleanup 1 - fi - if [ -n "$DEBUG" ]; then - log_info "D: Solarenergy data downloaded to file '$file3'." - fi - fi -} - -get_current_awattar_day() { current_awattar_day=$(sed -n 3p $file1 | grep -Eo '[0-9]+'); } -get_current_awattar_day2() { current_awattar_day2=$(sed -n 3p $file2 | grep -Eo '[0-9]+'); } - -get_awattar_prices() { - current_price=$(sed -n $((2 * $(TZ=$TZ date +%k) + 39))p $file1 | grep -Eo '[+-]?[0-9]+([.][0-9]+)?' | tail -n1) - lowest_price=$(sed -n 1p "$file7") - second_lowest_price=$(sed -n 2p "$file7") - third_lowest_price=$(sed -n 3p "$file7") - fourth_lowest_price=$(sed -n 4p "$file7") - fifth_lowest_price=$(sed -n 5p "$file7") - sixth_lowest_price=$(sed -n 6p "$file7") - # highest_price=$(awk '/^[0-9]+(\.[0-9]+)?$/ && $1 > max { max = $1 } END { print max }' "$file7") - # average_price=$(awk '/^[0-9]+(\.[0-9]+)?$/{sum+=$1; count++} END {if (count > 0) print sum/count}' "$file7") - highest_price=$(grep -E '^[0-9]+\.[0-9]+$' "$file7" | tail -n1) - average_price=$(grep -E '^[0-9]+\.[0-9]+$' "$file7" | awk '{sum+=$1; count++} END {if (count > 0) print sum/count}') -} - -get_tibber_prices() { - current_price=$(sed -n "${now_linenumber}s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file15") - lowest_price=$(sed -n "1s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") - second_lowest_price=$(sed -n "2s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") - third_lowest_price=$(sed -n "3s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") - fourth_lowest_price=$(sed -n "4s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") - fifth_lowest_price=$(sed -n "5s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") - sixth_lowest_price=$(sed -n "6s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") - highest_price=$(sed -n "s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12" | awk 'BEGIN {max = 0} {if ($1 > max) max = $1} END {print max}') - average_price=$(sed -n "s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12" | awk '{sum += $1} END {print sum/NR}') -} - -get_current_entsoe_day() { current_entsoe_day=$(sed -n 25p "$file10" | grep -Eo '[0-9]+'); } - -get_current_tibber_day() { current_tibber_day=$(sed -n 25p "$file15" | grep -Eo '[0-9]+'); } - -get_entsoe_prices() { - current_price=$(sed -n ${now_linenumber}p "$file10") - lowest_price=$(sed -n 1p "$file19") - second_lowest_price=$(sed -n 2p "$file19") - third_lowest_price=$(sed -n 3p "$file19") - fourth_lowest_price=$(sed -n 4p "$file19") - fifth_lowest_price=$(sed -n 5p "$file19") - sixth_lowest_price=$(sed -n 6p "$file19") - highest_price=$(awk 'BEGIN {max = 0} $1>max {max=$1} END {print max}' "$file19") - average_price=$(awk 'NF>0 && $1 ~ /^[0-9]*(\.[0-9]*)?$/ {sum+=$1; count++} END {if (count > 0) print sum/count}' "$file19") -} - -convert_vars_to_integer() { - local potency="$1" - shift - for var in "$@"; do - local integer_var="${var}_integer" - printf -v "$integer_var" '%s' "$(euroToMillicent "${!var}" "$potency")" - local value="${!integer_var}" # Speichern Sie den Wert in einer temporären Variable - if [ -n "$DEBUG" ]; then - log_info "D: Variable: $var | Original: ${!var} | Integer: $value | Len: ${#value}" >&2 - fi - done -} - -get_awattar_prices_integer() { - convert_vars_to_integer 15 lowest_price average_price highest_price second_lowest_price third_lowest_price fourth_lowest_price fifth_lowest_price sixth_lowest_price current_price stop_price start_price feedin_price energy_fee abort_price battery_lifecycle_costs_cent_per_kwh -} - -get_tibber_prices_integer() { - convert_vars_to_integer 17 lowest_price average_price highest_price second_lowest_price third_lowest_price fourth_lowest_price fifth_lowest_price sixth_lowest_price current_price - convert_vars_to_integer 15 stop_price start_price feedin_price energy_fee abort_price battery_lifecycle_costs_cent_per_kwh -} - -get_prices_integer_entsoe() { - convert_vars_to_integer 14 lowest_price average_price highest_price second_lowest_price third_lowest_price fourth_lowest_price fifth_lowest_price sixth_lowest_price current_price - convert_vars_to_integer 15 stop_price start_price feedin_price energy_fee abort_price battery_lifecycle_costs_cent_per_kwh -} - -get_solarenergy_today() { - solarenergy_today=$(sed '2!d' $file3 | cut -d',' -f2) - solarenergy_today_integer=$(euroToMillicent "${solarenergy_today}" 15) - abort_solar_yield_today_integer=$(euroToMillicent "${abort_solar_yield_today}" 15) -} - -get_solarenergy_tomorrow() { - solarenergy_tomorrow=$(sed '3!d' $file3 | cut -d',' -f2) - solarenergy_tomorrow_integer=$(euroToMillicent "$solarenergy_tomorrow" 15) - abort_solar_yield_tomorrow_integer=$(euroToMillicent "${abort_solar_yield_tomorrow}" 15) -} - -get_cloudcover_today() { - cloudcover_today=$(sed '2!d' $file3 | cut -d',' -f1) -} - -get_cloudcover_tomorrow() { - cloudcover_tomorrow=$(sed '3!d' $file3 | cut -d',' -f1) -} - -get_sunrise_today() { - sunrise_today=$(sed '2!d' $file3 | cut -d',' -f3 | cut -d 'T' -f2 | awk -F: '{ print $1 ":" $2 }') -} - -get_sunset_today() { - sunset_today=$(sed '2!d' $file3 | cut -d',' -f4 | cut -d 'T' -f2 | awk -F: '{ print $1 ":" $2 }') -} - -get_suntime_today() { - suntime_today=$((($(TZ=$TZ date -d "1970-01-01 $sunset_today" +%s) - $(TZ=$TZ date -d "1970-01-01 $sunrise_today" +%s)) / 60)) -} - -# Function to evaluate charging and switchablesockets conditions -evaluate_conditions() { - local -n conditions_ref="$1" - local -n descriptions_ref="$2" - local -n execute_ref="$3" - local -n condition_met_ref="$4" - local condition_met=0 # checkflag - - for condition in "${!conditions_ref[@]}"; do - if [ -n "$DEBUG" ]; then - description_value="${descriptions_ref[$condition]}" - condition_evaluation=$([ "${conditions_ref[$condition]}" -eq 1 ] && echo true || echo false) - result="($description_value) evaluates to $condition_evaluation" - log_info "D: condition_evaluation [ $result ]." >&2 - fi - - if ((conditions_ref[$condition])) && [[ $condition_met -eq 0 ]]; then - execute_ref=1 - condition_met_ref="$condition" - condition_met=1 - - if [[ $DEBUG -ne 1 ]]; then - break - fi - fi - done -} - -# Function to check economical -is_charging_economical() { - # In the Bash scripting environment, true represents a command that always ends with a success status (exit code 0), and false is a command that always ends with a failure status (exit code 1). - # In the context of comparisons or conditions in Bash: - - # A success status (e.g. a command's exit code 0) is often interpreted as "true". - # A failure status (e.g. any exit code other than 0) is often interpreted as "false". - - # In many programming languages, true represents the value 1 and false represents the value 0, but in the Bash scripting environment things are a little different as it involves the exit code of commands. - # For this reason, a value of 1 is output as false - - local reference_price="$1" - local total_cost="$2" - - local is_economical=1 - [[ $reference_price -ge $total_cost ]] && is_economical=0 - - if [ -n "$DEBUG" ]; then - log_info "D: is_charging_economical [ $is_economical - $([ "$is_economical" -eq 1 ] && echo "false" || echo "true") ]." >&2 - reference_price_euro=$(millicentToEuro $reference_price) - total_cost_euro=$(millicentToEuro $total_cost) - is_economical_str=$([ "$is_economical" -eq 1 ] && echo "false" || echo "true") - log_info "D: if [ reference_price $reference_price_euro > total_cost $total_cost_euro ] result is $is_economical_str." >&2 - fi - - return $is_economical -} - -# Function to manage charging -manage_charging() { - local action=$1 - local reason=$2 - - if [[ $action == "on" ]]; then - $charger_command_turnon >/dev/null - log_info "I: Victron scheduled charging is ON. Battery SOC is at $SOC_percent %. $reason" - else - $charger_command_turnoff >/dev/null - log_info "I: Victron scheduled charging is OFF. Battery SOC is at $SOC_percent %. $reason" - fi -} - -# Function to check abort conditions and log a message -check_abort_condition() { - local condition_result=$1 - local log_message=$2 - - if ((condition_result)); then - log_info "I: $log_message Abort." - execute_charging=0 - execute_switchablesockets_on=0 - fi -} - -# Function to manage fritz sockets and log a message -manage_fritz_sockets() { - local action=$1 - - [ "$action" != "off" ] && action=$([ "$execute_switchablesockets_on" == "1" ] && echo "on" || echo "off") - - if fritz_login; then - log_info "I: Turning $action Fritz sockets." - for socket in "${sockets[@]}"; do - [ "$socket" != "0" ] && manage_fritz_socket "$action" "$socket" - done - else - log_info "E: Fritz login failed." - fi -} - -manage_fritz_socket() { - local action=$1 - local socket=$2 - local url="http://$fbox/webservices/homeautoswitch.lua?sid=$sid&ain=$socket&switchcmd=setswitch$action" - curl -s "$url" >/dev/null || log_info "E: Could not call URL '$url' to switch $action said switch - ignored." -} - -fritz_login() { - # Get session ID (SID) - sid="" - challenge=$(curl -s "http://$fbox/login_sid.lua" | grep -o "[a-z0-9]\{8\}" | cut -d'>' -f 2) - if [ -z "$challenge" ]; then - log_info "E: Could not retrieve challenge from login_sid.lua." - return 1 - fi - - hash=$(echo -n "$challenge-$passwd" | sed -e 's,.,&\n,g' | tr '\n' '\0' | md5sum | grep -o "[0-9a-z]\{32\}") - sid=$(curl -s "http://$fbox/login_sid.lua" -d "response=$challenge-$hash" -d "username=$user" | - grep -o "[a-z0-9]\{16\}" | cut -d'>' -f 2) - - if [ "$sid" = "0000000000000000" ]; then - log_info "E: Login to Fritz!Box failed." - return 1 - fi - - if [ -n "$DEBUG" ]; then - log_info "D: Login to Fritz!Box successful." >&2 - fi - return 0 -} - -# Function to manage shelly and log a message -manage_shelly_sockets() { - local action=$1 - - [ "$action" != "off" ] && action=$([ "$execute_switchablesockets_on" == "1" ] && echo "on" || echo "off") - - log_info "I: Turning $action Shelly sockets." - for ip in "${shelly_ips[@]}"; do - [ "$ip" != "0" ] && manage_shelly_socket "$action" "$ip" - done -} - -manage_shelly_socket() { - local action=$1 - local ip=$2 - curl -s -u "$shellyuser:$shellypasswd" "http://$ip/relay/0?turn=$action" -o /dev/null || log_info "E: Could not execute switch-$action of Shelly socket with IP $ip - ignored." -} - -millicentToEuro() { - local millicents="$1" - - local EURO_FACTOR=100000000000000000 - local DECIMAL_FACTOR=10000000000000 - - local euro_main_part=$((millicents / EURO_FACTOR)) - local euro_decimal_part=$(((millicents % EURO_FACTOR) / DECIMAL_FACTOR)) - - printf "%d.%04d\n" $euro_main_part $euro_decimal_part -} - -euroToMillicent() { - euro="$1" - potency="$2" - - if [ -z "$potency" ]; then - potency=14 - fi - - #if echo "$euro" | grep -q '\,'; then - # echo "E: Could not translate '$euro' to an integer since this has a comma when only a period is accepted as decimal separator." - # return 1 - #fi - - # Replace each comma with a period, fixme if this is wrong - euro=$(echo "$euro" | sed 's/,/./g') - - # v=$(awk "BEGIN {print int($euro * (10 ^ $potency))}") - v=$(awk -v euro="$euro" -v potency="$potency" 'BEGIN {printf "%.0f", euro * (10 ^ potency)}') - - if [ -z "$v" ]; then - log_info "E: Could not translate '$euro' to an integer." - log_info "E: Called from ${FUNCNAME[1]} at line ${BASH_LINENO[0]}" - return 1 - fi - echo "$v" - return 0 -} - -euroToMillicent_test() { - log_info "I: Testing euroToMillicent" false - for i in 123456 12345.6 1234.56 123.456 12.3456 1.23456 0.123456 .123456 .233 .23 .2 2.33 2.3 2 2,33 2,3 2 23; do - echo -n "$i -> " - euroToMillicent $i - done -} - -log_info() { - local msg="$1" - local prefix=$(echo "$msg" | head -n 1 | cut -d' ' -f1) # Extract the first word from the first line - local color="\033[1m" # Default color - local writeToLog=true # Default is true - - case "$prefix" in - "E:") color="\033[1;31m" ;; # Bright Red - "D:") - color="\033[1;34m" # Bright Blue - writeToLog=false - ;; # Default to not log debug messages - "W:") color="\033[1;33m" ;; # Bright Yellow - "I:") color="\033[1;32m" ;; # Bright Green - esac - - writeToLog="${2:-$writeToLog}" # Override default if second parameter is provided - - # Print to console with color codes - printf "${color}%b\033[0m\n" "$msg" - - # If we should write to the log, write without color codes - if [ "$writeToLog" == "true" ]; then - echo -e "$msg" | sed 's/\x1b\[[0-9;]*m//g' >>"$LOG_FILE" - fi -} - -exit_with_cleanup() { - log_info "I: Cleanup and exit with error $1" - manage_charging "off" "Turn off charging." - manage_fritz_sockets "off" - manage_shelly_sockets "off" - exit $1 -} - -#################################### -### Begin of the script... ### -#################################### - -echo >>"$LOG_FILE" - -log_info "I: Bash Version: $(bash --version | head -n 1)" -log_info "I: Spotmarket-Switcher - Version $VERSION" - -parse_and_validate_config "$DIR/config.txt" -# if [ $? -eq 1 ]; then -# Handle error -# fi - -# An independent segment to test the conversion of floats to integers -if [ "tests" == "$1" ]; then - euroToMillicent_test - exit 0 -fi - -if ((select_pricing_api == 1)); then - # Test if Awattar today data exists - if test -f "$file1"; then - # Test if data is current - get_current_awattar_day - if [ "$current_awattar_day" = "$(TZ=$TZ date +%-d)" ]; then - log_info "I: aWATTar today-data is up to date." false - else - log_info "I: aWATTar today-data is outdated, fetching new data." false - rm -f $file1 $file6 $file7 - download_awattar_prices "$link1" "$file1" "$file6" $((RANDOM % 21 + 10)) - fi - else # Data file1 does not exist - log_info "I: Fetching today-data data from aWATTar." false - download_awattar_prices "$link1" "$file1" "$file6" $((RANDOM % 21 + 10)) - fi - -elif ((select_pricing_api == 2)); then - # Test if Entsoe today data exists - if test -f "$file10"; then - # Test if data is current - get_current_entsoe_day - if [ "$current_entsoe_day" = "$(TZ=$TZ date +%d)" ]; then - log_info "I: Entsoe today-data is up to date." false - else - log_info "I: Entsoe today-data is outdated, fetching new data." false - rm -f "$file4" "$file5" "$file8" "$file9" "$file10" "$file11" "$file13" "$file19" - download_entsoe_prices "$link4" "$file4" "$file10" $((RANDOM % 21 + 10)) - fi - else # Entsoe data does not exist - log_info "I: Fetching today-data data from Entsoe." false - download_entsoe_prices "$link4" "$file4" "$file10" $((RANDOM % 21 + 10)) - fi - -elif ((select_pricing_api == 3)); then - - # Test if Tibber today data exists - if test -f "$file14"; then - # Test if data is current - get_current_tibber_day - if [ "$current_tibber_day" = "$(TZ=$TZ date +%d)" ]; then - log_info "I: Tibber today-data is up to date." false - else - log_info "I: Tibber today-data is outdated, fetching new data." false - rm -f "$file14" "$file15" "$file16" - download_tibber_prices "$link6" "$file14" $((RANDOM % 21 + 10)) - fi - else # Tibber data does not exist - log_info "I: Fetching today-data data from Tibber." false - download_tibber_prices "$link6" "$file14" $((RANDOM % 21 + 10)) - fi -fi - -if ((include_second_day == 1)); then - - if ((select_pricing_api == 1)); then - - # Test if Awattar tomorrow data exists - if test -f "$file2"; then - # Test if data is current - get_current_awattar_day2 - if [ "$current_awattar_day2" = "$(TZ=$TZ date +%-d)" ]; then - log_info "I: aWATTar tomorrow-data is up to date." false - else - log_info "I: aWATTar tomorrow-data is outdated, fetching new data." false - rm -f $file3 - download_awattar_prices "$link2" "$file2" "$file6" $((RANDOM % 21 + 10)) - fi - else # Data file2 does not exist - log_info "I: aWATTar tomorrow-data does not exist, fetching data." false - download_awattar_prices "$link2" "$file2" "$file6" $((RANDOM % 21 + 10)) - fi - - elif ((select_pricing_api == 2)); then - - # Test if Entsoe tomorrow data exists - if [ ! -s "$file9" ]; then - log_info "I: File '$file9' has no tomorrow data, we have to try it again until the new prices are online." false - rm -f "$file5" "$file9" "$file13" - download_entsoe_prices "$link5" "$file5" "$file13" $((RANDOM % 21 + 10)) - fi - - elif ((select_pricing_api == 3)); then - - if [ ! -s "$file18" ]; then - rm -f "$file17" "$file18" - log_info "I: File '$file18' has no tomorrow data, we have to try it again until the new prices are online." false - rm -f "$file12" "$file14" "$file15" "$file16" "$file17" - download_tibber_prices "$link6" "$file14" $((RANDOM % 21 + 10)) - sort -t, -k1.9n $file17 >>"$file12" - fi - - fi - -fi # Include second day - -if ((select_pricing_api == 1)); then - Unit="Cent/kWh net" - get_awattar_prices - get_awattar_prices_integer -elif ((select_pricing_api == 2)); then - Unit="EUR/MWh net" - get_entsoe_prices - get_prices_integer_entsoe -elif ((select_pricing_api == 3)); then - Unit="EUR/kWh $tibber_prices price" - get_tibber_prices - get_tibber_prices_integer -fi - -if ((use_solarweather_api_to_abort == 1)); then - download_solarenergy - get_solarenergy_today - get_solarenergy_tomorrow - get_cloudcover_today - get_cloudcover_tomorrow - get_sunrise_today - get_sunset_today - get_suntime_today -fi - -log_info "I: Please verify correct system time and timezone:\n $(TZ=$TZ date)" -echo -log_info "I: Current price is $current_price $Unit." -log_info "I: Lowest price will be $lowest_price $Unit." false -log_info "I: The average price will be $average_price $Unit." false -log_info "I: Highest price will be $highest_price $Unit." false -log_info "I: Second lowest price will be $second_lowest_price $Unit." false -log_info "I: Third lowest price will be $third_lowest_price $Unit." false -log_info "I: Fourth lowest price will be $fourth_lowest_price $Unit." false -log_info "I: Fifth lowest price will be $fifth_lowest_price $Unit." false -log_info "I: Sixth lowest price will be $sixth_lowest_price $Unit." false - -if ((use_solarweather_api_to_abort == 1)); then - log_info "I: Sunrise today will be $sunrise_today and sunset will be $sunset_today. Suntime will be $suntime_today minutes." - log_info "I: Solarenergy today will be $solarenergy_today megajoule per sqaremeter with $cloudcover_today percent clouds." - log_info "I: Solarenergy tomorrow will be $solarenergy_tomorrow megajoule per squaremeter with $cloudcover_tomorrow percent clouds." - if [ ! -s $file3 ]; then - log_info "E: File '$file3' is empty, please check your API Key if download is still not possible tomorrow." - fi - find "$file3" -size 0 -delete # FIXME - looks wrong and complicated - simple RM included in prior if clause? -else - log_info "W: skip Solarweather. not activated" -fi - -charging_condition_met="" -execute_charging=0 -execute_switchablesockets_on=0 - -declare -A charging_conditions_descriptions=( - ["use_start_stop_logic"]="use_start_stop_logic ($use_start_stop_logic) == 1 && start_price_integer ($start_price_integer) > current_price_integer ($current_price_integer)" - ["charge_at_solar_breakeven_logic"]="charge_at_solar_breakeven_logic ($charge_at_solar_breakeven_logic) == 1 && feedin_price_integer ($feedin_price_integer) > current_price_integer ($current_price_integer) + energy_fee_integer ($energy_fee_integer)" - ["charge_at_lowest_price"]="charge_at_lowest_price ($charge_at_lowest_price) == 1 && lowest_price_integer ($lowest_price_integer) == current_price_integer ($current_price_integer)" - ["charge_at_second_lowest_price"]="charge_at_second_lowest_price ($charge_at_second_lowest_price) == 1 && second_lowest_price_integer ($second_lowest_price_integer) == current_price_integer ($current_price_integer)" - ["charge_at_third_lowest_price"]="charge_at_third_lowest_price ($charge_at_third_lowest_price) == 1 && third_lowest_price_integer ($third_lowest_price_integer) == current_price_integer ($current_price_integer)" - ["charge_at_fourth_lowest_price"]="charge_at_fourth_lowest_price ($charge_at_fourth_lowest_price) == 1 && fourth_lowest_price_integer ($fourth_lowest_price_integer) == current_price_integer ($current_price_integer)" - ["charge_at_fifth_lowest_price"]="charge_at_fifth_lowest_price ($charge_at_fifth_lowest_price) == 1 && fifth_lowest_price_integer ($fifth_lowest_price_integer) == current_price_integer ($current_price_integer)" - ["charge_at_sixth_lowest_price"]="charge_at_sixth_lowest_price ($charge_at_sixth_lowest_price) == 1 && sixth_lowest_price_integer ($sixth_lowest_price_integer) == current_price_integer ($current_price_integer)" -) - -declare -A charging_conditions=( - ["use_start_stop_logic"]=$((use_start_stop_logic == 1 && start_price_integer > current_price_integer)) - ["charge_at_solar_breakeven_logic"]=$((charge_at_solar_breakeven_logic == 1 && feedin_price_integer > current_price_integer + energy_fee_integer)) - ["charge_at_lowest_price"]=$((charge_at_lowest_price == 1 && lowest_price_integer == current_price_integer)) - ["charge_at_second_lowest_price"]=$((charge_at_second_lowest_price == 1 && second_lowest_price_integer == current_price_integer)) - ["charge_at_third_lowest_price"]=$((charge_at_third_lowest_price == 1 && third_lowest_price_integer == current_price_integer)) - ["charge_at_fourth_lowest_price"]=$((charge_at_fourth_lowest_price == 1 && fourth_lowest_price_integer == current_price_integer)) - ["charge_at_fifth_lowest_price"]=$((charge_at_fifth_lowest_price == 1 && fifth_lowest_price_integer == current_price_integer)) - ["charge_at_sixth_lowest_price"]=$((charge_at_sixth_lowest_price == 1 && sixth_lowest_price_integer == current_price_integer)) -) - -# Check if any charging condition is met -evaluate_conditions charging_conditions charging_conditions_descriptions execute_charging charging_condition_met - -declare -A switchablesockets_conditions_descriptions=( - ["switchablesockets_at_start_stop"]="switchablesockets_at_start_stop ($switchablesockets_at_start_stop) == 1 && start_price_integer ($start_price_integer) > current_price_integer ($current_price_integer)" - ["switchablesockets_at_solar_breakeven_logic"]="switchablesockets_at_solar_breakeven_logic ($switchablesockets_at_solar_breakeven_logic) == 1 && feedin_price_integer ($feedin_price_integer) > current_price_integer ($current_price_integer) + energy_fee_integer ($energy_fee_integer)" - ["switchablesockets_at_lowest_price"]="switchablesockets_at_lowest_price ($switchablesockets_at_lowest_price) == 1 && lowest_price_integer ($lowest_price_integer) == current_price_integer ($current_price_integer)" - ["switchablesockets_at_second_lowest_price"]="switchablesockets_at_second_lowest_price ($switchablesockets_at_second_lowest_price) == 1 && second_lowest_price_integer ($second_lowest_price_integer) == current_price_integer ($current_price_integer)" - ["switchablesockets_at_third_lowest_price"]="switchablesockets_at_third_lowest_price ($switchablesockets_at_third_lowest_price) == 1 && third_lowest_price_integer ($third_lowest_price_integer) == current_price_integer ($current_price_integer)" - ["switchablesockets_at_fourth_lowest_price"]="switchablesockets_at_fourth_lowest_price ($switchablesockets_at_fourth_lowest_price) == 1 && fourth_lowest_price_integer ($fourth_lowest_price_integer) == current_price_integer ($current_price_integer)" - ["switchablesockets_at_fifth_lowest_price"]="switchablesockets_at_fifth_lowest_price ($switchablesockets_at_fifth_lowest_price) == 1 && fifth_lowest_price_integer ($fifth_lowest_price_integer) == current_price_integer ($current_price_integer)" - ["switchablesockets_at_sixth_lowest_price"]="switchablesockets_at_sixth_lowest_price ($switchablesockets_at_sixth_lowest_price) == 1 && sixth_lowest_price_integer ($sixth_lowest_price_integer) == current_price_integer ($current_price_integer)" -) - -declare -A switchablesockets_conditions=( - ["switchablesockets_at_start_stop"]=$((switchablesockets_at_start_stop == 1 && start_price_integer > current_price_integer)) - ["switchablesockets_at_solar_breakeven_logic"]=$((switchablesockets_at_solar_breakeven_logic == 1 && feedin_price_integer > current_price_integer + energy_fee_integer)) - ["switchablesockets_at_lowest_price"]=$((switchablesockets_at_lowest_price == 1 && lowest_price_integer == current_price_integer)) - ["switchablesockets_at_second_lowest_price"]=$((switchablesockets_at_second_lowest_price == 1 && second_lowest_price_integer == current_price_integer)) - ["switchablesockets_at_third_lowest_price"]=$((switchablesockets_at_third_lowest_price == 1 && third_lowest_price_integer == current_price_integer)) - ["switchablesockets_at_fourth_lowest_price"]=$((switchablesockets_at_fourth_lowest_price == 1 && fourth_lowest_price_integer == current_price_integer)) - ["switchablesockets_at_fifth_lowest_price"]=$((switchablesockets_at_fifth_lowest_price == 1 && fifth_lowest_price_integer == current_price_integer)) - ["switchablesockets_at_sixth_lowest_price"]=$((switchablesockets_at_sixth_lowest_price == 1 && sixth_lowest_price_integer == current_price_integer)) -) - -# Check if any switching condition is met -evaluate_conditions switchablesockets_conditions switchablesockets_conditions_descriptions execute_switchablesockets_on switchablesockets_condition_met - -if ((use_solarweather_api_to_abort == 1)); then - check_abort_condition $((abort_suntime <= suntime_today)) "There are enough sun minutes today." - check_abort_condition $((abort_solar_yield_today_integer <= solarenergy_today_integer)) "There is enough solarenergy today." - check_abort_condition $((abort_solar_yield_tomorrow_integer <= solarenergy_tomorrow_integer)) "There is enough sun tomorrow." -fi - -# abort_price_integer cannot be found by shellcheck can be ignored, false positive -check_abort_condition $((abort_price_integer <= current_price_integer)) "Current price ($(millicentToEuro "$current_price_integer")€) is too high. Abort. ($(millicentToEuro "$abort_price_integer")€)" - -# If any charging condition is met, start charging -percent_of_current_price_integer=$(awk "BEGIN {print $current_price_integer*$energy_loss_percent/100}" | printf "%.0f") -total_cost_integer=$((current_price_integer + percent_of_current_price_integer + battery_lifecycle_costs_cent_per_kwh_integer)) - -if ((execute_charging == 1 && use_victron_charger == 1)); then - if [ "$economic_check" -eq 0 ]; then - manage_charging "on" "Charging based on condition met of: $charging_condition_met." - elif [ "$economic_check" -eq 1 ] && is_charging_economical $highest_price_integer $total_cost_integer; then - manage_charging "on" "Charging based on highest price ($(millicentToEuro "$highest_price_integer") €) comparison makes sense. total_cost=$(millicentToEuro "$total_cost_integer") €" - elif [ "$economic_check" -eq 2 ] && is_charging_economical $average_price_integer $total_cost_integer; then - manage_charging "on" "Charging based on average price ($(millicentToEuro "$average_price_integer") €) comparison makes sense. total_cost=$(millicentToEuro "$total_cost_integer") €" - else - reason_msg="Considering charging losses and costs, charging is too expensive." - - [ "$economic_check" -eq 1 ] && reason_msg="Charging is too expensive based on the highest price ($(millicentToEuro "$highest_price_integer") €) comparison." - [ "$economic_check" -eq 2 ] && reason_msg="Charging is too expensive based on the average price ($(millicentToEuro "$average_price_integer") €) comparison." - - manage_charging "off" "$reason_msg (total_cost=$(millicentToEuro "$total_cost_integer") €)" - fi -elif ((execute_charging != 1 && use_victron_charger == 1)); then - manage_charging "off" "Charging was not executed." -else - log_info "W: skip Victron Charger. not activated" -fi - -# Execute Fritz DECT on command -if ((use_fritz_dect_sockets == 1)); then - manage_fritz_sockets -else - log_info "W: skip Fritz DECT. not activated" -fi - -if ((use_shelly_wlan_sockets == 1)); then - manage_shelly_sockets -else - log_info "W: skip Shelly Api. not activated" -fi - -echo >>"$LOG_FILE" - -# Rotating log files -if [ -f "$LOG_FILE" ]; then - if [ "$(du -k "$LOG_FILE" | awk '{print $1}')" -gt "$LOG_MAX_SIZE" ]; then - log_info "I: Rotating log files" - mv "$LOG_FILE" "${LOG_FILE}.$(date +%Y%m%d%H%M%S)" - touch "$LOG_FILE" - find . -maxdepth 1 -name "${LOG_FILE}*" -type f -exec ls -1t {} + | - sed 's|^\./||' | - tail -n +$((LOG_FILES_TO_KEEP + 1)) | - xargs --no-run-if-empty rm - fi -fi - -if [ -n "$DEBUG" ]; then - log_info "D: \[ OK \]" >&2 -fi From f2fbfd743ec56fa04540dcd77de14e8b14b099fc Mon Sep 17 00:00:00 2001 From: Christian Kvasny Date: Tue, 24 Oct 2023 00:40:18 +0200 Subject: [PATCH 22/24] Create controller.sh deleted by mistake --- scripts/controller.sh | 1219 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1219 insertions(+) create mode 100644 scripts/controller.sh diff --git a/scripts/controller.sh b/scripts/controller.sh new file mode 100644 index 0000000..243de36 --- /dev/null +++ b/scripts/controller.sh @@ -0,0 +1,1219 @@ +#!/bin/bash + +License=$( + cat </dev/null; then + log_info "E: Please ensure the tool '$tool' is found." + num_tools_missing=$((num_tools_missing + 1)) + fi +done + +if [ $num_tools_missing -gt 0 ]; then + log_info "E: $num_tools_missing tools are missing." + exit 127 +fi + +unset num_tools_missing + +####################################### +### Begin of the functions... ### +####################################### + +declare -A valid_vars=( + ["use_fritz_dect_sockets"]="0|1" + ["fbox"]="^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" + ["user"]="string" + ["passwd"]="string" + ["sockets"]='^\(\"[^"]+\"( \"[^"]+\")*\)$' + ["use_shelly_wlan_sockets"]="0|1" + ["shelly_ips"]="^\(\".*\"\)$" + ["shellyuser"]="string" + ["shellypasswd"]="string" + ["use_victron_charger"]="0|1" + ["energy_loss_percent"]="[0-9]+(\.[0-9]+)?" + ["battery_lifecycle_costs_cent_per_kwh"]="[0-9]+(\.[0-9]+)?" + ["economic_check"]="0|1|2" + ["stop_price"]="[0-9]+(\.[0-9]+)?" + ["start_price"]="[0-9]+(\.[0-9]+)?" + ["feedin_price"]="[0-9]+(\.[0-9]+)?" + ["energy_fee"]="[0-9]+(\.[0-9]+)?" + ["abort_price"]="[0-9]+(\.[0-9]+)?" + ["use_start_stop_logic"]="0|1" + ["switchablesockets_at_start_stop"]="0|1" + ["charge_at_solar_breakeven_logic"]="0|1" + ["switchablesockets_at_solar_breakeven_logic"]="0|1" + ["charge_at_lowest_price"]="0|1" + ["switchablesockets_at_lowest_price"]="0|1" + ["charge_at_second_lowest_price"]="0|1" + ["switchablesockets_at_second_lowest_price"]="0|1" + ["charge_at_third_lowest_price"]="0|1" + ["switchablesockets_at_third_lowest_price"]="0|1" + ["charge_at_fourth_lowest_price"]="0|1" + ["switchablesockets_at_fourth_lowest_price"]="0|1" + ["charge_at_fifth_lowest_price"]="0|1" + ["switchablesockets_at_fifth_lowest_price"]="0|1" + ["charge_at_sixth_lowest_price"]="0|1" + ["switchablesockets_at_sixth_lowest_price"]="0|1" + ["TZ"]="string" + ["select_pricing_api"]="1|2|3" + ["include_second_day"]="0|1" + ["use_solarweather_api_to_abort"]="0|1" + ["abort_solar_yield_today"]="[0-9]+(\.[0-9]+)?" + ["abort_solar_yield_tomorrow"]="[0-9]+(\.[0-9]+)?" + ["abort_suntime"]="[0-9]+" + ["latitude"]="[-]?[0-9]+(\.[0-9]+)?" + ["longitude"]="[-]?[0-9]+(\.[0-9]+)?" + ["visualcrossing_api_key"]="string" + ["awattar"]="de|at" + ["in_Domain"]="string" + ["out_Domain"]="string" + ["entsoe_eu_api_security_token"]="string" + ["tibber_prices"]="energy|total|tax" + ["tibber_api_key"]="string" +) + +declare -A config_values + +parse_and_validate_config() { + local file="$1" + local errors="" + + rotating_spinner & # Start the spinner in the background + local spinner_pid=$! # Get the PID of the spinner + + # Step 1: Parse + while IFS='=' read -r key value; do + # Treat everything after a "#" as a comment and remove it + key=$(echo "$key" | cut -d'#' -f1 | tr -d ' ') + value=$(echo "$value" | awk -F'#' '{gsub(/^ *"|"$|^ *| *$/, "", $1); print $1}') + + # Only process rows with key-value pairs + [[ "$key" == "" || "$value" == "" ]] && continue + + # Set the value in the associative array + config_values["$key"]="$value" + done <"$file" + + # Step 2: Validation + for var_name in "${!valid_vars[@]}"; do + local validation_pattern=${valid_vars[$var_name]} + + # Check whether the variable was set at all + if [[ -z ${config_values[$var_name]+x} ]]; then + errors+="E: $var_name is not set.\n" + continue + fi + + # Special checking for strings, IP, and arrays + if [[ "$validation_pattern" == "string" ]]; then + # Strings can be empty or filled + continue + elif [[ "$validation_pattern" == "array" && "${config_values[$var_name]}" == "" ]]; then + continue + elif [[ "$validation_pattern" == "ip" && ! "${config_values[$var_name]}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + errors+="E: $var_name has an invalid IP address format: ${config_values[$var_name]}.\n" + continue + fi + + # Standard check against the given pattern + if ! [[ "${config_values[$var_name]}" =~ ^($validation_pattern)$ ]]; then + errors+="E: $var_name has an invalid value: ${config_values[$var_name]}.\n" + fi + done + + # Stop the spinner once the parsing is done + kill $spinner_pid &>/dev/null + + # Output errors if any were found + if [[ -n "$errors" ]]; then + echo -e "$errors" + return 1 + else + echo "Config validation passed." + return 0 + fi +} + +rotating_spinner() { + local delay=0.1 + local spinstr="|/-\\" + while true; do + local temp=${spinstr#?} + printf " [%c] Loading..." "$spinstr" + spinstr=$temp${spinstr%"$temp"} + sleep $delay + printf "\r" + done +} + +download_awattar_prices() { + local url="$1" + local file="$2" + local output_file="$3" + local sleep_time="$4" + + if [ -z "$DEBUG" ]; then + log_info "I: Please be patient. First we wait $sleep_time seconds in case the system clock is not syncronized and not to overload the API." false + sleep "$sleep_time" + fi + if ! curl "$url" >"$file"; then + log_info "E: Download of aWATTar prices from '$url' to '$file' failed." + exit_with_cleanup 1 + fi + + if ! test -f "$file"; then + log_info "E: Could not get aWATTar prices from '$url' to feed file '$file'." + exit_with_cleanup 1 + fi + + if [ -n "$DEBUG" ]; then + log_info "D: Download of file '$file' from URL '$url' successful." >&2 + fi + echo >>"$file" + awk '/data_price_hour_rel_.*_amount: / {print substr($0, index($0, ":") + 2)}' "$file" >"$output_file" + sort -g "$output_file" >"${output_file%.*}_sorted.${output_file##*.}" + timestamp=$(TZ=$TZ date +%d) + echo "date_now_day: $timestamp" >>"$output_file" + echo "date_now_day: $timestamp" >>"${output_file%.*}_sorted.${output_file##*.}" + + if [ -f "$file2" ] && [ "$(wc -l <"$file1")" = "$(wc -l <"$file2")" ]; then + rm -f "$file2" + log_info "I: File '$file2' has no tomorrow data, we have to try it again until the new prices are online." false + fi +} + +get_tibber_api() { + curl --location --request POST 'https://api.tibber.com/v1-beta/gql' \ + --header 'Content-Type: application/json' \ + --header "Authorization: Bearer $tibber_api_key" \ + --data-raw '{"query":"{viewer{homes{currentSubscription{priceInfo{current{total energy tax startsAt}today{total energy tax startsAt}tomorrow{total energy tax startsAt}}}}}}"}' | + awk '{ + gsub(/"current":/, "\n&"); + gsub(/"today":/, "\n&"); + gsub(/"tomorrow":/, "\n&"); + gsub(/"total":/, "\n&"); + print + }' +} + +download_tibber_prices() { + local url="$1" + local file="$2" + local sleep_time="$3" + + if [ -z "$DEBUG" ]; then + log_info "I: Please be patient. First we wait $sleep_time seconds in case the system clock is not syncronized and not to overload the API." false + sleep "$sleep_time" + else + log_info "D: No delay of download of Tibber data since DEBUG variable set." + fi + if ! get_tibber_api | tr -d '{}[]' >"$file"; then + log_info "E: Download of Tibber prices from '$url' to '$file' failed." + exit_with_cleanup 1 + fi + + sed -n '/"today":/,/"tomorrow":/p' "$file" | sed '$d' | sed '/"today":/d' >"$file15" + sort -t, -k1.9n $file15 >"$file16" + sed -n '/"tomorrow":/,$p' "$file" | sed '/"tomorrow":/d' >"$file17" + sort -t, -k1.9n $file17 >"$file18" + if [ "$include_second_day" = 0 ]; then + cp "$file16" "$file12" + else + grep '"total"' "$file14" | sort -t':' -k2 -n >"$file12" + fi + + timestamp=$(TZ=$TZ date +%d) + echo "date_now_day: $timestamp" >>"$file15" + echo "date_now_day: $timestamp" >>"$file17" + + if [ ! -s "$file16" ]; then + log_info "E: Tibber prices cannot be extracted to '$file16', please check your Tibber API Key." + rm "$file" + exit_with_cleanup 1 + fi +} + +download_entsoe_prices() { + local url="$1" + local file="$2" + local output_file="$3" + local sleep_time="$4" + + if [ -z "$DEBUG" ]; then + log_info "I: Please be patient. First we wait $sleep_time seconds in case the system clock is not syncronized and not to overload the API." false + sleep "$sleep_time" + else + log_info "D: No delay of download of entsoe data since DEBUG variable set." >&2 + fi + + if ! curl "$url" >"$file"; then + log_info "E: Retrieval of entsoe data from '$url' into file '$file' failed." + exit_with_cleanup 1 + fi + + if ! test -f "$file"; then + log_info "E: Could not find file '$file' with entsoe price data. Curl itself reported success." + exit_with_cleanup 1 + fi + + if [ -n "$DEBUG" ]; then log_info "D: No delay of download of entsoe data since DEBUG variable set." "D: Entsoe file '$file' with price data downloaded" >&2 >&2; fi + + if [ ! -s "$file" ]; then + log_info "E: Entsoe file '$file' is empty, please check your entsoe API Key." + exit_with_cleanup 1 + fi + + if [ -n "$DEBUG" ]; then log_info "D: No delay of download of entsoe data since DEBUG variable set." "D: Entsoe file '$file' with price data downloaded" >&2; fi + + awk ' + # Capture content inside the tag + // { + capture_period = 1 + } + /<\/Period>/ { + capture_period = 0 + } + # Ensure we are within a valid period and capture prices for one-hour resolution + capture_period && /PT60M<\/resolution>/ { + valid_period = 1 + } + valid_period && // { + gsub("", "", $0) + gsub("<\/price.amount>", "", $0) + gsub(/^[\t ]+|[\t ]+$/, "", $0) + prices = prices $0 ORS + } + valid_period && /<\/Period>/ { + exit + } + + # Capture error information inside the tag + // { + in_reason = 1 + error_message = "" + } + in_reason && // { + gsub(/|<\/code>/, "") + gsub(/^[\t ]+|[\t ]+$/, "", $0) + error_code = $0 + } + in_reason && // { + gsub(/|<\/text>/, "") + gsub(/^[\t ]+|[\t ]+$/, "", $0) + error_message = $0 + } + /<\/Reason>/ { + in_reason = 0 + } + + # At the end of processing, print out the captured prices or any error messages + END { + if (error_code == 999) { + log_info "E: Entsoe data retrieval error:", error_message + exit_with_cleanup 1 + } else if (prices != "") { + printf "%s", prices > "'"$output_file"'" + } else { + if ("'"$output_file"'" != "'"$file13"'") { + log_info "E: No prices found in the today XML data." + exit_with_cleanup 1 + } + } + log_info "E: No prices found in the tomorrow XML data." + } + ' "$file" + + if [ -f "$output_file" ]; then + sort -g "$output_file" >"${output_file%.*}_sorted.${output_file##*.}" + timestamp=$(TZ=$TZ date +%d) + echo "date_now_day: $timestamp" >>"$output_file" + fi + + # Check if tomorrow file contains next day prices + if [ "$include_second_day" = 1 ] && grep -q "PT60M" "$file" && [ "$(wc -l <"$output_file")" -gt 3 ]; then + cat $file10 >$file8 + # echo >> $file8 + if [ -f "$file13" ]; then + cat "$file13" >>"$file8" + fi + sed -i '25d 50d' "$file8" + sort -g "$file8" >"$file19" + timestamp=$(TZ=$TZ date +%d) + echo "date_now_day: $timestamp" >>"$file8" + else + cp $file11 $file19 # If no second day, copy sorted price file. + fi +} + +download_solarenergy() { + if ((use_solarweather_api_to_abort == 1)); then + delay=$((RANDOM % 15 + 1)) + if [ -z "$DEBUG" ]; then + log_info "I: Please be patient. A delay of $delay seconds will help avoid overloading the Solarweather-API." false + # Delaying a random time <=15s to reduce impact on site - download is not time-critical + sleep "$delay" + else + log_info "D: No delay of download of solarenergy data since DEBUG variable set." >&2 + fi + if ! curl "$link3" -o "$file3"; then + log_info "E: Download of solarenergy data from '$link3' failed." + exit_with_cleanup 1 + elif ! test -f "$file3"; then + log_info "E: Could not get solarenergy data, missing file '$file3'." + exit_with_cleanup 1 + fi + if [ -n "$DEBUG" ]; then + log_info "D: File3 $file3 downloaded" >&2 + fi + if ! test -f "$file3"; then + log_info "E: Could not find downloaded file '$file3' with solarenergy data." + exit_with_cleanup 1 + fi + if [ -n "$DEBUG" ]; then + log_info "D: Solarenergy data downloaded to file '$file3'." + fi + fi +} + +get_current_awattar_day() { current_awattar_day=$(sed -n 3p $file1 | grep -Eo '[0-9]+'); } +get_current_awattar_day2() { current_awattar_day2=$(sed -n 3p $file2 | grep -Eo '[0-9]+'); } + +get_awattar_prices() { + current_price=$(sed -n $((2 * $(TZ=$TZ date +%k) + 39))p $file1 | grep -Eo '[+-]?[0-9]+([.][0-9]+)?' | tail -n1) + lowest_price=$(sed -n 1p "$file7") + second_lowest_price=$(sed -n 2p "$file7") + third_lowest_price=$(sed -n 3p "$file7") + fourth_lowest_price=$(sed -n 4p "$file7") + fifth_lowest_price=$(sed -n 5p "$file7") + sixth_lowest_price=$(sed -n 6p "$file7") + # highest_price=$(awk '/^[0-9]+(\.[0-9]+)?$/ && $1 > max { max = $1 } END { print max }' "$file7") + # average_price=$(awk '/^[0-9]+(\.[0-9]+)?$/{sum+=$1; count++} END {if (count > 0) print sum/count}' "$file7") + highest_price=$(grep -E '^[0-9]+\.[0-9]+$' "$file7" | tail -n1) + average_price=$(grep -E '^[0-9]+\.[0-9]+$' "$file7" | awk '{sum+=$1; count++} END {if (count > 0) print sum/count}') +} + +get_tibber_prices() { + current_price=$(sed -n "${now_linenumber}s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file15") + lowest_price=$(sed -n "1s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") + second_lowest_price=$(sed -n "2s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") + third_lowest_price=$(sed -n "3s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") + fourth_lowest_price=$(sed -n "4s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") + fifth_lowest_price=$(sed -n "5s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") + sixth_lowest_price=$(sed -n "6s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12") + highest_price=$(sed -n "s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12" | awk 'BEGIN {max = 0} {if ($1 > max) max = $1} END {print max}') + average_price=$(sed -n "s/.*\"${tibber_prices}\":([^,]*),.*/\1/p" "$file12" | awk '{sum += $1} END {print sum/NR}') +} + +get_current_entsoe_day() { current_entsoe_day=$(sed -n 25p "$file10" | grep -Eo '[0-9]+'); } + +get_current_tibber_day() { current_tibber_day=$(sed -n 25p "$file15" | grep -Eo '[0-9]+'); } + +get_entsoe_prices() { + current_price=$(sed -n ${now_linenumber}p "$file10") + lowest_price=$(sed -n 1p "$file19") + second_lowest_price=$(sed -n 2p "$file19") + third_lowest_price=$(sed -n 3p "$file19") + fourth_lowest_price=$(sed -n 4p "$file19") + fifth_lowest_price=$(sed -n 5p "$file19") + sixth_lowest_price=$(sed -n 6p "$file19") + highest_price=$(awk 'BEGIN {max = 0} $1>max {max=$1} END {print max}' "$file19") + average_price=$(awk 'NF>0 && $1 ~ /^[0-9]*(\.[0-9]*)?$/ {sum+=$1; count++} END {if (count > 0) print sum/count}' "$file19") +} + +convert_vars_to_integer() { + local potency="$1" + shift + for var in "$@"; do + local integer_var="${var}_integer" + printf -v "$integer_var" '%s' "$(euroToMillicent "${!var}" "$potency")" + local value="${!integer_var}" # Speichern Sie den Wert in einer temporären Variable + if [ -n "$DEBUG" ]; then + log_info "D: Variable: $var | Original: ${!var} | Integer: $value | Len: ${#value}" >&2 + fi + done +} + +get_awattar_prices_integer() { + convert_vars_to_integer 15 lowest_price average_price highest_price second_lowest_price third_lowest_price fourth_lowest_price fifth_lowest_price sixth_lowest_price current_price stop_price start_price feedin_price energy_fee abort_price battery_lifecycle_costs_cent_per_kwh +} + +get_tibber_prices_integer() { + convert_vars_to_integer 17 lowest_price average_price highest_price second_lowest_price third_lowest_price fourth_lowest_price fifth_lowest_price sixth_lowest_price current_price + convert_vars_to_integer 15 stop_price start_price feedin_price energy_fee abort_price battery_lifecycle_costs_cent_per_kwh +} + +get_prices_integer_entsoe() { + convert_vars_to_integer 14 lowest_price average_price highest_price second_lowest_price third_lowest_price fourth_lowest_price fifth_lowest_price sixth_lowest_price current_price + convert_vars_to_integer 15 stop_price start_price feedin_price energy_fee abort_price battery_lifecycle_costs_cent_per_kwh +} + +get_solarenergy_today() { + solarenergy_today=$(sed '2!d' $file3 | cut -d',' -f2) + solarenergy_today_integer=$(euroToMillicent "${solarenergy_today}" 15) + abort_solar_yield_today_integer=$(euroToMillicent "${abort_solar_yield_today}" 15) +} + +get_solarenergy_tomorrow() { + solarenergy_tomorrow=$(sed '3!d' $file3 | cut -d',' -f2) + solarenergy_tomorrow_integer=$(euroToMillicent "$solarenergy_tomorrow" 15) + abort_solar_yield_tomorrow_integer=$(euroToMillicent "${abort_solar_yield_tomorrow}" 15) +} + +get_cloudcover_today() { + cloudcover_today=$(sed '2!d' $file3 | cut -d',' -f1) +} + +get_cloudcover_tomorrow() { + cloudcover_tomorrow=$(sed '3!d' $file3 | cut -d',' -f1) +} + +get_sunrise_today() { + sunrise_today=$(sed '2!d' $file3 | cut -d',' -f3 | cut -d 'T' -f2 | awk -F: '{ print $1 ":" $2 }') +} + +get_sunset_today() { + sunset_today=$(sed '2!d' $file3 | cut -d',' -f4 | cut -d 'T' -f2 | awk -F: '{ print $1 ":" $2 }') +} + +get_suntime_today() { + suntime_today=$((($(TZ=$TZ date -d "1970-01-01 $sunset_today" +%s) - $(TZ=$TZ date -d "1970-01-01 $sunrise_today" +%s)) / 60)) +} + +# Function to evaluate charging and switchablesockets conditions +evaluate_conditions() { + local -n conditions_ref="$1" + local -n descriptions_ref="$2" + local -n execute_ref="$3" + local -n condition_met_ref="$4" + local condition_met=0 # checkflag + + for condition in "${!conditions_ref[@]}"; do + if [ -n "$DEBUG" ]; then + description_value="${descriptions_ref[$condition]}" + condition_evaluation=$([ "${conditions_ref[$condition]}" -eq 1 ] && echo true || echo false) + result="($description_value) evaluates to $condition_evaluation" + log_info "D: condition_evaluation [ $result ]." >&2 + fi + + if ((conditions_ref[$condition])) && [[ $condition_met -eq 0 ]]; then + execute_ref=1 + condition_met_ref="$condition" + condition_met=1 + + if [[ $DEBUG -ne 1 ]]; then + break + fi + fi + done +} + +# Function to check economical +is_charging_economical() { + # In the Bash scripting environment, true represents a command that always ends with a success status (exit code 0), and false is a command that always ends with a failure status (exit code 1). + # In the context of comparisons or conditions in Bash: + + # A success status (e.g. a command's exit code 0) is often interpreted as "true". + # A failure status (e.g. any exit code other than 0) is often interpreted as "false". + + # In many programming languages, true represents the value 1 and false represents the value 0, but in the Bash scripting environment things are a little different as it involves the exit code of commands. + # For this reason, a value of 1 is output as false + + local reference_price="$1" + local total_cost="$2" + + local is_economical=1 + [[ $reference_price -ge $total_cost ]] && is_economical=0 + + if [ -n "$DEBUG" ]; then + log_info "D: is_charging_economical [ $is_economical - $([ "$is_economical" -eq 1 ] && echo "false" || echo "true") ]." >&2 + reference_price_euro=$(millicentToEuro $reference_price) + total_cost_euro=$(millicentToEuro $total_cost) + is_economical_str=$([ "$is_economical" -eq 1 ] && echo "false" || echo "true") + log_info "D: if [ reference_price $reference_price_euro > total_cost $total_cost_euro ] result is $is_economical_str." >&2 + fi + + return $is_economical +} + +# Function to manage charging +manage_charging() { + local action=$1 + local reason=$2 + + if [[ $action == "on" ]]; then + $charger_command_turnon >/dev/null + log_info "I: Victron scheduled charging is ON. Battery SOC is at $SOC_percent %. $reason" + else + $charger_command_turnoff >/dev/null + log_info "I: Victron scheduled charging is OFF. Battery SOC is at $SOC_percent %. $reason" + fi +} + +# Function to check abort conditions and log a message +check_abort_condition() { + local condition_result=$1 + local log_message=$2 + + if ((condition_result)); then + log_info "I: $log_message Abort." + execute_charging=0 + execute_switchablesockets_on=0 + fi +} + +# Function to manage fritz sockets and log a message +manage_fritz_sockets() { + local action=$1 + + [ "$action" != "off" ] && action=$([ "$execute_switchablesockets_on" == "1" ] && echo "on" || echo "off") + + if fritz_login; then + log_info "I: Turning $action Fritz sockets." + for socket in "${sockets[@]}"; do + [ "$socket" != "0" ] && manage_fritz_socket "$action" "$socket" + done + else + log_info "E: Fritz login failed." + fi +} + +manage_fritz_socket() { + local action=$1 + local socket=$2 + local url="http://$fbox/webservices/homeautoswitch.lua?sid=$sid&ain=$socket&switchcmd=setswitch$action" + curl -s "$url" >/dev/null || log_info "E: Could not call URL '$url' to switch $action said switch - ignored." +} + +fritz_login() { + # Get session ID (SID) + sid="" + challenge=$(curl -s "http://$fbox/login_sid.lua" | grep -o "[a-z0-9]\{8\}" | cut -d'>' -f 2) + if [ -z "$challenge" ]; then + log_info "E: Could not retrieve challenge from login_sid.lua." + return 1 + fi + + hash=$(echo -n "$challenge-$passwd" | sed -e 's,.,&\n,g' | tr '\n' '\0' | md5sum | grep -o "[0-9a-z]\{32\}") + sid=$(curl -s "http://$fbox/login_sid.lua" -d "response=$challenge-$hash" -d "username=$user" | + grep -o "[a-z0-9]\{16\}" | cut -d'>' -f 2) + + if [ "$sid" = "0000000000000000" ]; then + log_info "E: Login to Fritz!Box failed." + return 1 + fi + + if [ -n "$DEBUG" ]; then + log_info "D: Login to Fritz!Box successful." >&2 + fi + return 0 +} + +# Function to manage shelly and log a message +manage_shelly_sockets() { + local action=$1 + + [ "$action" != "off" ] && action=$([ "$execute_switchablesockets_on" == "1" ] && echo "on" || echo "off") + + log_info "I: Turning $action Shelly sockets." + for ip in "${shelly_ips[@]}"; do + [ "$ip" != "0" ] && manage_shelly_socket "$action" "$ip" + done +} + +manage_shelly_socket() { + local action=$1 + local ip=$2 + curl -s -u "$shellyuser:$shellypasswd" "http://$ip/relay/0?turn=$action" -o /dev/null || log_info "E: Could not execute switch-$action of Shelly socket with IP $ip - ignored." +} + +millicentToEuro() { + local millicents="$1" + + local EURO_FACTOR=100000000000000000 + local DECIMAL_FACTOR=10000000000000 + + local euro_main_part=$((millicents / EURO_FACTOR)) + local euro_decimal_part=$(((millicents % EURO_FACTOR) / DECIMAL_FACTOR)) + + printf "%d.%04d\n" $euro_main_part $euro_decimal_part +} + +euroToMillicent() { + euro="$1" + potency="$2" + + if [ -z "$potency" ]; then + potency=14 + fi + + #if echo "$euro" | grep -q '\,'; then + # echo "E: Could not translate '$euro' to an integer since this has a comma when only a period is accepted as decimal separator." + # return 1 + #fi + + # Replace each comma with a period, fixme if this is wrong + euro=$(echo "$euro" | sed 's/,/./g') + + # v=$(awk "BEGIN {print int($euro * (10 ^ $potency))}") + v=$(awk -v euro="$euro" -v potency="$potency" 'BEGIN {printf "%.0f", euro * (10 ^ potency)}') + + if [ -z "$v" ]; then + log_info "E: Could not translate '$euro' to an integer." + log_info "E: Called from ${FUNCNAME[1]} at line ${BASH_LINENO[0]}" + return 1 + fi + echo "$v" + return 0 +} + +euroToMillicent_test() { + log_info "I: Testing euroToMillicent" false + for i in 123456 12345.6 1234.56 123.456 12.3456 1.23456 0.123456 .123456 .233 .23 .2 2.33 2.3 2 2,33 2,3 2 23; do + echo -n "$i -> " + euroToMillicent $i + done +} + +log_info() { + local msg="$1" + local prefix=$(echo "$msg" | head -n 1 | cut -d' ' -f1) # Extract the first word from the first line + local color="\033[1m" # Default color + local writeToLog=true # Default is true + + case "$prefix" in + "E:") color="\033[1;31m" ;; # Bright Red + "D:") + color="\033[1;34m" # Bright Blue + writeToLog=false + ;; # Default to not log debug messages + "W:") color="\033[1;33m" ;; # Bright Yellow + "I:") color="\033[1;32m" ;; # Bright Green + esac + + writeToLog="${2:-$writeToLog}" # Override default if second parameter is provided + + # Print to console with color codes + printf "${color}%b\033[0m\n" "$msg" + + # If we should write to the log, write without color codes + if [ "$writeToLog" == "true" ]; then + echo -e "$msg" | sed 's/\x1b\[[0-9;]*m//g' >>"$LOG_FILE" + fi +} + +exit_with_cleanup() { + log_info "I: Cleanup and exit with error $1" + manage_charging "off" "Turn off charging." + manage_fritz_sockets "off" + manage_shelly_sockets "off" + exit $1 +} + +#################################### +### Begin of the script... ### +#################################### + +echo >>"$LOG_FILE" + +log_info "I: Bash Version: $(bash --version | head -n 1)" +log_info "I: Spotmarket-Switcher - Version $VERSION" + +parse_and_validate_config "$DIR/config.txt" +# if [ $? -eq 1 ]; then +# Handle error +# fi + +# An independent segment to test the conversion of floats to integers +if [ "tests" == "$1" ]; then + euroToMillicent_test + exit 0 +fi + +if ((select_pricing_api == 1)); then + # Test if Awattar today data exists + if test -f "$file1"; then + # Test if data is current + get_current_awattar_day + if [ "$current_awattar_day" = "$(TZ=$TZ date +%-d)" ]; then + log_info "I: aWATTar today-data is up to date." false + else + log_info "I: aWATTar today-data is outdated, fetching new data." false + rm -f $file1 $file6 $file7 + download_awattar_prices "$link1" "$file1" "$file6" $((RANDOM % 21 + 10)) + fi + else # Data file1 does not exist + log_info "I: Fetching today-data data from aWATTar." false + download_awattar_prices "$link1" "$file1" "$file6" $((RANDOM % 21 + 10)) + fi + +elif ((select_pricing_api == 2)); then + # Test if Entsoe today data exists + if test -f "$file10"; then + # Test if data is current + get_current_entsoe_day + if [ "$current_entsoe_day" = "$(TZ=$TZ date +%d)" ]; then + log_info "I: Entsoe today-data is up to date." false + else + log_info "I: Entsoe today-data is outdated, fetching new data." false + rm -f "$file4" "$file5" "$file8" "$file9" "$file10" "$file11" "$file13" "$file19" + download_entsoe_prices "$link4" "$file4" "$file10" $((RANDOM % 21 + 10)) + fi + else # Entsoe data does not exist + log_info "I: Fetching today-data data from Entsoe." false + download_entsoe_prices "$link4" "$file4" "$file10" $((RANDOM % 21 + 10)) + fi + +elif ((select_pricing_api == 3)); then + + # Test if Tibber today data exists + if test -f "$file14"; then + # Test if data is current + get_current_tibber_day + if [ "$current_tibber_day" = "$(TZ=$TZ date +%d)" ]; then + log_info "I: Tibber today-data is up to date." false + else + log_info "I: Tibber today-data is outdated, fetching new data." false + rm -f "$file14" "$file15" "$file16" + download_tibber_prices "$link6" "$file14" $((RANDOM % 21 + 10)) + fi + else # Tibber data does not exist + log_info "I: Fetching today-data data from Tibber." false + download_tibber_prices "$link6" "$file14" $((RANDOM % 21 + 10)) + fi +fi + +if ((include_second_day == 1)); then + + if ((select_pricing_api == 1)); then + + # Test if Awattar tomorrow data exists + if test -f "$file2"; then + # Test if data is current + get_current_awattar_day2 + if [ "$current_awattar_day2" = "$(TZ=$TZ date +%-d)" ]; then + log_info "I: aWATTar tomorrow-data is up to date." false + else + log_info "I: aWATTar tomorrow-data is outdated, fetching new data." false + rm -f $file3 + download_awattar_prices "$link2" "$file2" "$file6" $((RANDOM % 21 + 10)) + fi + else # Data file2 does not exist + log_info "I: aWATTar tomorrow-data does not exist, fetching data." false + download_awattar_prices "$link2" "$file2" "$file6" $((RANDOM % 21 + 10)) + fi + + elif ((select_pricing_api == 2)); then + + # Test if Entsoe tomorrow data exists + if [ ! -s "$file9" ]; then + log_info "I: File '$file9' has no tomorrow data, we have to try it again until the new prices are online." false + rm -f "$file5" "$file9" "$file13" + download_entsoe_prices "$link5" "$file5" "$file13" $((RANDOM % 21 + 10)) + fi + + elif ((select_pricing_api == 3)); then + + if [ ! -s "$file18" ]; then + rm -f "$file17" "$file18" + log_info "I: File '$file18' has no tomorrow data, we have to try it again until the new prices are online." false + rm -f "$file12" "$file14" "$file15" "$file16" "$file17" + download_tibber_prices "$link6" "$file14" $((RANDOM % 21 + 10)) + sort -t, -k1.9n $file17 >>"$file12" + fi + + fi + +fi # Include second day + +if ((select_pricing_api == 1)); then + Unit="Cent/kWh net" + get_awattar_prices + get_awattar_prices_integer +elif ((select_pricing_api == 2)); then + Unit="EUR/MWh net" + get_entsoe_prices + get_prices_integer_entsoe +elif ((select_pricing_api == 3)); then + Unit="EUR/kWh $tibber_prices price" + get_tibber_prices + get_tibber_prices_integer +fi + +if ((use_solarweather_api_to_abort == 1)); then + download_solarenergy + get_solarenergy_today + get_solarenergy_tomorrow + get_cloudcover_today + get_cloudcover_tomorrow + get_sunrise_today + get_sunset_today + get_suntime_today +fi + +log_info "I: Please verify correct system time and timezone:\n $(TZ=$TZ date)" +echo +log_info "I: Current price is $current_price $Unit." +log_info "I: Lowest price will be $lowest_price $Unit." false +log_info "I: The average price will be $average_price $Unit." false +log_info "I: Highest price will be $highest_price $Unit." false +log_info "I: Second lowest price will be $second_lowest_price $Unit." false +log_info "I: Third lowest price will be $third_lowest_price $Unit." false +log_info "I: Fourth lowest price will be $fourth_lowest_price $Unit." false +log_info "I: Fifth lowest price will be $fifth_lowest_price $Unit." false +log_info "I: Sixth lowest price will be $sixth_lowest_price $Unit." false + +if ((use_solarweather_api_to_abort == 1)); then + log_info "I: Sunrise today will be $sunrise_today and sunset will be $sunset_today. Suntime will be $suntime_today minutes." + log_info "I: Solarenergy today will be $solarenergy_today megajoule per sqaremeter with $cloudcover_today percent clouds." + log_info "I: Solarenergy tomorrow will be $solarenergy_tomorrow megajoule per squaremeter with $cloudcover_tomorrow percent clouds." + if [ ! -s $file3 ]; then + log_info "E: File '$file3' is empty, please check your API Key if download is still not possible tomorrow." + fi + find "$file3" -size 0 -delete # FIXME - looks wrong and complicated - simple RM included in prior if clause? +else + log_info "W: skip Solarweather. not activated" +fi + +charging_condition_met="" +execute_charging=0 +execute_switchablesockets_on=0 + +declare -A charging_conditions_descriptions=( + ["use_start_stop_logic"]="use_start_stop_logic ($use_start_stop_logic) == 1 && start_price_integer ($start_price_integer) > current_price_integer ($current_price_integer)" + ["charge_at_solar_breakeven_logic"]="charge_at_solar_breakeven_logic ($charge_at_solar_breakeven_logic) == 1 && feedin_price_integer ($feedin_price_integer) > current_price_integer ($current_price_integer) + energy_fee_integer ($energy_fee_integer)" + ["charge_at_lowest_price"]="charge_at_lowest_price ($charge_at_lowest_price) == 1 && lowest_price_integer ($lowest_price_integer) == current_price_integer ($current_price_integer)" + ["charge_at_second_lowest_price"]="charge_at_second_lowest_price ($charge_at_second_lowest_price) == 1 && second_lowest_price_integer ($second_lowest_price_integer) == current_price_integer ($current_price_integer)" + ["charge_at_third_lowest_price"]="charge_at_third_lowest_price ($charge_at_third_lowest_price) == 1 && third_lowest_price_integer ($third_lowest_price_integer) == current_price_integer ($current_price_integer)" + ["charge_at_fourth_lowest_price"]="charge_at_fourth_lowest_price ($charge_at_fourth_lowest_price) == 1 && fourth_lowest_price_integer ($fourth_lowest_price_integer) == current_price_integer ($current_price_integer)" + ["charge_at_fifth_lowest_price"]="charge_at_fifth_lowest_price ($charge_at_fifth_lowest_price) == 1 && fifth_lowest_price_integer ($fifth_lowest_price_integer) == current_price_integer ($current_price_integer)" + ["charge_at_sixth_lowest_price"]="charge_at_sixth_lowest_price ($charge_at_sixth_lowest_price) == 1 && sixth_lowest_price_integer ($sixth_lowest_price_integer) == current_price_integer ($current_price_integer)" +) + +declare -A charging_conditions=( + ["use_start_stop_logic"]=$((use_start_stop_logic == 1 && start_price_integer > current_price_integer)) + ["charge_at_solar_breakeven_logic"]=$((charge_at_solar_breakeven_logic == 1 && feedin_price_integer > current_price_integer + energy_fee_integer)) + ["charge_at_lowest_price"]=$((charge_at_lowest_price == 1 && lowest_price_integer == current_price_integer)) + ["charge_at_second_lowest_price"]=$((charge_at_second_lowest_price == 1 && second_lowest_price_integer == current_price_integer)) + ["charge_at_third_lowest_price"]=$((charge_at_third_lowest_price == 1 && third_lowest_price_integer == current_price_integer)) + ["charge_at_fourth_lowest_price"]=$((charge_at_fourth_lowest_price == 1 && fourth_lowest_price_integer == current_price_integer)) + ["charge_at_fifth_lowest_price"]=$((charge_at_fifth_lowest_price == 1 && fifth_lowest_price_integer == current_price_integer)) + ["charge_at_sixth_lowest_price"]=$((charge_at_sixth_lowest_price == 1 && sixth_lowest_price_integer == current_price_integer)) +) + +# Check if any charging condition is met +evaluate_conditions charging_conditions charging_conditions_descriptions execute_charging charging_condition_met + +declare -A switchablesockets_conditions_descriptions=( + ["switchablesockets_at_start_stop"]="switchablesockets_at_start_stop ($switchablesockets_at_start_stop) == 1 && start_price_integer ($start_price_integer) > current_price_integer ($current_price_integer)" + ["switchablesockets_at_solar_breakeven_logic"]="switchablesockets_at_solar_breakeven_logic ($switchablesockets_at_solar_breakeven_logic) == 1 && feedin_price_integer ($feedin_price_integer) > current_price_integer ($current_price_integer) + energy_fee_integer ($energy_fee_integer)" + ["switchablesockets_at_lowest_price"]="switchablesockets_at_lowest_price ($switchablesockets_at_lowest_price) == 1 && lowest_price_integer ($lowest_price_integer) == current_price_integer ($current_price_integer)" + ["switchablesockets_at_second_lowest_price"]="switchablesockets_at_second_lowest_price ($switchablesockets_at_second_lowest_price) == 1 && second_lowest_price_integer ($second_lowest_price_integer) == current_price_integer ($current_price_integer)" + ["switchablesockets_at_third_lowest_price"]="switchablesockets_at_third_lowest_price ($switchablesockets_at_third_lowest_price) == 1 && third_lowest_price_integer ($third_lowest_price_integer) == current_price_integer ($current_price_integer)" + ["switchablesockets_at_fourth_lowest_price"]="switchablesockets_at_fourth_lowest_price ($switchablesockets_at_fourth_lowest_price) == 1 && fourth_lowest_price_integer ($fourth_lowest_price_integer) == current_price_integer ($current_price_integer)" + ["switchablesockets_at_fifth_lowest_price"]="switchablesockets_at_fifth_lowest_price ($switchablesockets_at_fifth_lowest_price) == 1 && fifth_lowest_price_integer ($fifth_lowest_price_integer) == current_price_integer ($current_price_integer)" + ["switchablesockets_at_sixth_lowest_price"]="switchablesockets_at_sixth_lowest_price ($switchablesockets_at_sixth_lowest_price) == 1 && sixth_lowest_price_integer ($sixth_lowest_price_integer) == current_price_integer ($current_price_integer)" +) + +declare -A switchablesockets_conditions=( + ["switchablesockets_at_start_stop"]=$((switchablesockets_at_start_stop == 1 && start_price_integer > current_price_integer)) + ["switchablesockets_at_solar_breakeven_logic"]=$((switchablesockets_at_solar_breakeven_logic == 1 && feedin_price_integer > current_price_integer + energy_fee_integer)) + ["switchablesockets_at_lowest_price"]=$((switchablesockets_at_lowest_price == 1 && lowest_price_integer == current_price_integer)) + ["switchablesockets_at_second_lowest_price"]=$((switchablesockets_at_second_lowest_price == 1 && second_lowest_price_integer == current_price_integer)) + ["switchablesockets_at_third_lowest_price"]=$((switchablesockets_at_third_lowest_price == 1 && third_lowest_price_integer == current_price_integer)) + ["switchablesockets_at_fourth_lowest_price"]=$((switchablesockets_at_fourth_lowest_price == 1 && fourth_lowest_price_integer == current_price_integer)) + ["switchablesockets_at_fifth_lowest_price"]=$((switchablesockets_at_fifth_lowest_price == 1 && fifth_lowest_price_integer == current_price_integer)) + ["switchablesockets_at_sixth_lowest_price"]=$((switchablesockets_at_sixth_lowest_price == 1 && sixth_lowest_price_integer == current_price_integer)) +) + +# Check if any switching condition is met +evaluate_conditions switchablesockets_conditions switchablesockets_conditions_descriptions execute_switchablesockets_on switchablesockets_condition_met + +if ((use_solarweather_api_to_abort == 1)); then + check_abort_condition $((abort_suntime <= suntime_today)) "There are enough sun minutes today." + check_abort_condition $((abort_solar_yield_today_integer <= solarenergy_today_integer)) "There is enough solarenergy today." + check_abort_condition $((abort_solar_yield_tomorrow_integer <= solarenergy_tomorrow_integer)) "There is enough sun tomorrow." +fi + +# abort_price_integer cannot be found by shellcheck can be ignored, false positive +check_abort_condition $((abort_price_integer <= current_price_integer)) "Current price ($(millicentToEuro "$current_price_integer")€) is too high. Abort. ($(millicentToEuro "$abort_price_integer")€)" + +# If any charging condition is met, start charging +percent_of_current_price_integer=$(awk "BEGIN {print $current_price_integer*$energy_loss_percent/100}" | printf "%.0f") +total_cost_integer=$((current_price_integer + percent_of_current_price_integer + battery_lifecycle_costs_cent_per_kwh_integer)) + +if ((execute_charging == 1 && use_victron_charger == 1)); then + if [ "$economic_check" -eq 0 ]; then + manage_charging "on" "Charging based on condition met of: $charging_condition_met." + elif [ "$economic_check" -eq 1 ] && is_charging_economical $highest_price_integer $total_cost_integer; then + manage_charging "on" "Charging based on highest price ($(millicentToEuro "$highest_price_integer") €) comparison makes sense. total_cost=$(millicentToEuro "$total_cost_integer") €" + elif [ "$economic_check" -eq 2 ] && is_charging_economical $average_price_integer $total_cost_integer; then + manage_charging "on" "Charging based on average price ($(millicentToEuro "$average_price_integer") €) comparison makes sense. total_cost=$(millicentToEuro "$total_cost_integer") €" + else + reason_msg="Considering charging losses and costs, charging is too expensive." + + [ "$economic_check" -eq 1 ] && reason_msg="Charging is too expensive based on the highest price ($(millicentToEuro "$highest_price_integer") €) comparison." + [ "$economic_check" -eq 2 ] && reason_msg="Charging is too expensive based on the average price ($(millicentToEuro "$average_price_integer") €) comparison." + + manage_charging "off" "$reason_msg (total_cost=$(millicentToEuro "$total_cost_integer") €)" + fi +elif ((execute_charging != 1 && use_victron_charger == 1)); then + manage_charging "off" "Charging was not executed." +else + log_info "W: skip Victron Charger. not activated" +fi + +# Execute Fritz DECT on command +if ((use_fritz_dect_sockets == 1)); then + manage_fritz_sockets +else + log_info "W: skip Fritz DECT. not activated" +fi + +if ((use_shelly_wlan_sockets == 1)); then + manage_shelly_sockets +else + log_info "W: skip Shelly Api. not activated" +fi + +echo >>"$LOG_FILE" + +# Rotating log files +if [ -f "$LOG_FILE" ]; then + if [ "$(du -k "$LOG_FILE" | awk '{print $1}')" -gt "$LOG_MAX_SIZE" ]; then + log_info "I: Rotating log files" + mv "$LOG_FILE" "${LOG_FILE}.$(date +%Y%m%d%H%M%S)" + touch "$LOG_FILE" + find . -maxdepth 1 -name "${LOG_FILE}*" -type f -exec ls -1t {} + | + sed 's|^\./||' | + tail -n +$((LOG_FILES_TO_KEEP + 1)) | + xargs --no-run-if-empty rm + fi +fi + +if [ -n "$DEBUG" ]; then + log_info "D: \[ OK \]" >&2 +fi From 0e934ecf86a4927afc14a38f07500a423df8144c Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 23 Oct 2023 22:40:30 +0000 Subject: [PATCH 23/24] Update version to 2.3.4-DEV --- scripts/controller.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/controller.sh b/scripts/controller.sh index 243de36..60a1cd0 100644 --- a/scripts/controller.sh +++ b/scripts/controller.sh @@ -28,7 +28,7 @@ License=$( EOLICENSE ) -VERSION="2.3.3-DEV" +VERSION="2.3.4-DEV" set -e From 5716b13a8ece3a9e1544fc2cdcef7de9ba1a40a3 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 23 Oct 2023 22:42:59 +0000 Subject: [PATCH 24/24] Update version to 2.3.4 --- scripts/controller.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/controller.sh b/scripts/controller.sh index 60a1cd0..ebb762b 100644 --- a/scripts/controller.sh +++ b/scripts/controller.sh @@ -28,7 +28,7 @@ License=$( EOLICENSE ) -VERSION="2.3.4-DEV" +VERSION="2.3.4" set -e