Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/time reserve battery triggering #56

Merged
merged 5 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Flags:
-k, --apikey string set APIKEY
-H, --fronius_ip string set FRONIUS_IP
-h, --help help for estimate
-u, --url string Set the URL. For multiple URLs, use a comma (,) to separate them
-u, --url string Set the forecast URL. For multiple URLs, use a comma (,) to separate them
```

## Schedule
Expand All @@ -98,17 +98,19 @@ Usage:
sbam schedule [flags]

Flags:
-k, --apikey string APIKEY
-t, --crontab string CRONTAB (default "0 0 0 0 0")
-d, --defaults DEFAULTS (default true)
-e, --end_hr string END_HR (default "05:55")
-H, --fronius_ip string FRONIUS_IP
-h, --help help for schedule
-m, --max_charge float MAX_CHARGE (default 3500)
-r, --pw_batt_reserve float PW_BATT_RESERVE
-c, --pw_consumption float PW_CONSUMPTION
-s, --start_hr string START_HR (default "00:00")
-u, --url string Set the URL. For multiple URLs, use a comma (,) to separate them
-k, --apikey string APIKEY
-E, --batt_reserve_end_hr string BATT_RESERVE_END_HR (default END_HR)
-S, --batt_reserve_start_hr string BATT_RESERVE_START_HR (default START_HR)
-t, --crontab string CRONTAB (default "0 0 0 0 0")
-d, --defaults DEFAULTS (default true)
-e, --end_hr string END_HR (default "00:55")
-H, --fronius_ip string FRONIUS_IP
-h, --help help for schedule
-m, --max_charge float MAX_CHARGE (default 3500)
-r, --pw_batt_reserve float PW_BATT_RESERVE
-c, --pw_consumption float PW_CONSUMPTION
-s, --start_hr string START_HR (default "00:00")
-u, --url string Set the Forecast URL. For multiple URLs, use a comma (,) to separate them
```

## Debug Logs
Expand Down
10 changes: 6 additions & 4 deletions home-assistant/addons/sbam/DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,15 @@ Do not start yet but configure it:
8. **pw_consumption:** Daily electrical consumption in Wh (Default: 11000, means 11kWh)
9. **max_charge:** Maximum amount of power required from the electricity network to charge the battery in W (Default: 3500)
10. **pw_batt_reserve:** Minimum battery capacity to maintain in Wh (Default: 4000, means 4kWh)
11. **defaults:** At the end of the crontab cycle, reconfigure the Fronius inverter to default (automatic management).
12. **reset:** At the add-on boot, reconfigure the Fronius inverter to its default settings.
13. **debug:** Increase the log level to debug, for example, printing Modbus read/write operations.
11. **batt_reserve_start_hr:** The start time to activate battery reserve charging (if empty default **start_hr**)
12. **batt_reserve_end_hr:** The end time to activate battery reserve charging (if empty default **end_hr**)
13. **defaults:** At the end of the crontab cycle, reconfigure the Fronius inverter to default (automatic management).
14. **reset:** At the add-on boot, reconfigure the Fronius inverter to its default settings.
15. **debug:** Increase the log level to debug, for example, printing Modbus read/write operations.

and save

![chrome_9b0blNNJ0n](https://github.com/user-attachments/assets/6244a7ba-e10c-44c9-8482-f7642ff6f7ee)
![chrome_VPrnrPIxah](https://github.com/user-attachments/assets/74582cc6-6a4d-481a-b3f9-b743c44b0087)


Finally Start **sbam**!
Expand Down
4 changes: 4 additions & 0 deletions home-assistant/addons/sbam/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"pw_consumption": "11000",
"max_charge": "3500",
"pw_batt_reserve": "4000",
"batt_reserve_start_hr": "",
"batt_reserve_end_hr": "",
"defaults": true,
"reset": false,
"debug": false
Expand All @@ -31,6 +33,8 @@
"pw_consumption": "float",
"max_charge": "float",
"pw_batt_reserve": "float",
"batt_reserve_start_hr": "match(^$|^([01]?[0-9]|2[0-3]):[0-5][0-9]$)",
"batt_reserve_end_hr": "match(^$|^([01]?[0-9]|2[0-3]):[0-5][0-9]$)",
"defaults": "bool",
"reset": "bool",
"debug": "bool"
Expand Down
30 changes: 8 additions & 22 deletions pkg/cmd/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"errors"
"os"
"sbam/pkg/fronius"
u "sbam/src/utils"
"strings"
Expand All @@ -11,7 +10,6 @@ import (
"github.com/spf13/viper"
)


var c_defaults bool
var force_charge bool
var power int
Expand All @@ -23,22 +21,10 @@ var cfgCmd = &cobra.Command{
Short: "Configure Battery Storage Charge",
Long: `connect via modbus to the fronius inverter and set charging`,
Run: func(cmd *cobra.Command, args []string) {
if len(fronius_ip) == 0 { fronius_ip = viper.GetString("fronius_ip") }
if !c_defaults {
if _, exists := os.LookupEnv("DEFAULTS"); exists {
c_defaults = viper.GetBool("defaults")
}
}
if !force_charge {
if _, exists := os.LookupEnv("FORCE_CHARGE"); exists {
force_charge = viper.GetBool("force_charge")
}
}
if power == const_pw {
if _, exists := os.LookupEnv("POWER"); exists {
power = viper.GetInt("power")
}
}
fronius_ip = viper.GetString("fronius_ip")
c_defaults = viper.GetBool("defaults")
force_charge = viper.GetBool("force_charge")
power = viper.GetInt("power")

err := checkConfigure(fronius_ip)
if err != nil {
Expand All @@ -52,10 +38,10 @@ var cfgCmd = &cobra.Command{
}

func init() {
cfgCmd.Flags().StringVarP(&fronius_ip,"fronius_ip", "H", "", "set FRONIUS_IP")
cfgCmd.Flags().BoolVarP(&c_defaults,"defaults", "d", false, "set DEFAULTS")
cfgCmd.Flags().BoolVarP(&force_charge,"force_charge", "f", false, "set FORCE_CHARGE")
cfgCmd.Flags().IntVarP(&power,"power", "p", const_pw, "set percent of nominal POWER")
cfgCmd.Flags().StringVarP(&fronius_ip, "fronius_ip", "H", "", "set FRONIUS_IP")
cfgCmd.Flags().BoolVarP(&c_defaults, "defaults", "d", false, "set DEFAULTS")
cfgCmd.Flags().BoolVarP(&force_charge, "force_charge", "f", false, "set FORCE_CHARGE")
cfgCmd.Flags().IntVarP(&power, "power", "p", const_pw, "set percent of nominal POWER")
viper.BindPFlag("fronius_ip", cfgCmd.Flags().Lookup("fronius_ip"))
viper.BindPFlag("defaults", scdCmd.Flags().Lookup("defaults"))
viper.BindPFlag("force_charge", cfgCmd.Flags().Lookup("force_charge"))
Expand Down
14 changes: 4 additions & 10 deletions pkg/cmd/estimate.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,9 @@ var estCmd = &cobra.Command{
Short: "Estimate Forecast Solar Power",
Long: `Print the solar forecast and the battery storage power`,
Run: func(cmd *cobra.Command, args []string) {
if len(e_url) == 0 {
e_url = viper.GetString("url")
}
if len(e_apiKey) == 0 {
e_apiKey = viper.GetString("apikey")
}
if len(fronius_ip) == 0 {
fronius_ip = viper.GetString("fronius_ip")
}
e_url = viper.GetString("url")
e_apiKey = viper.GetString("apikey")
fronius_ip = viper.GetString("fronius_ip")

err := CheckEstimate(e_apiKey, e_url, fronius_ip)
if err != nil {
Expand All @@ -40,7 +34,7 @@ var estCmd = &cobra.Command{
}

func init() {
estCmd.Flags().StringVarP(&e_url, "url", "u", "", "Set the URL. For multiple URLs, use a comma (,) to separate them")
estCmd.Flags().StringVarP(&e_url, "url", "u", "", "Set the forecast URL. For multiple URLs, use a comma (,) to separate them")
estCmd.Flags().StringVarP(&e_apiKey, "apikey", "k", "", "set APIKEY")
estCmd.Flags().StringVarP(&fronius_ip, "fronius_ip", "H", "", "set FRONIUS_IP")

Expand Down
126 changes: 70 additions & 56 deletions pkg/cmd/schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,67 +26,47 @@ var start_hr string
var end_hr string
var max_charge float64
var pw_batt_reserve float64
var batt_reserve_start_hr string
var batt_reserve_end_hr string
var crontab string
var s_defaults bool

const (
const_pc = 0.0
const_sh = "00:00"
const_eh = "05:55"
const_mc = 3500
const_pbr = 0
const_ct = "0 0 0 0 0"
const_pc = 0.0
const_sh = "00:00"
const_eh = "00:55"
const_mc = 3500
const_pbr = 0
const_br_sh = ""
const_br_eh = ""
const_ct = "0 0 0 0 0"
)

var scdCmd = &cobra.Command{
Use: "schedule",
Short: "Schedule Battery Storage Charge",
Long: `Workflow to Check Forecast and Battery residual Capacity and decide if it has to be charged in a definited time range`,
Run: func(cmd *cobra.Command, args []string) {
if len(s_url) == 0 {
s_url = viper.GetString("url")
}
if len(s_apiKey) == 0 {
s_apiKey = viper.GetString("apikey")
}
if len(fronius_ip) == 0 {
fronius_ip = viper.GetString("fronius_ip")
}
if pw_consumption == const_pc {
if _, exists := os.LookupEnv("PW_CONSUMPTION"); exists {
pw_consumption = viper.GetFloat64("pw_consumption")
}
}
if start_hr == const_sh {
if _, exists := os.LookupEnv("START_HR"); exists {
start_hr = viper.GetString("start_hr")
}
}
if end_hr == const_eh {
if _, exists := os.LookupEnv("END_HR"); exists {
end_hr = viper.GetString("end_hr")
}
}
if max_charge == const_mc {
if _, exists := os.LookupEnv("MAX_CHARGE"); exists {
max_charge = viper.GetFloat64("max_charge")
}
}
if pw_batt_reserve == const_pbr {
if _, exists := os.LookupEnv("PW_BATT_RESERVE"); exists {
pw_batt_reserve = viper.GetFloat64("pw_batt_reserve")
}
}
if crontab == const_ct {
if _, exists := os.LookupEnv("CRONTAB"); exists {
crontab = viper.GetString("crontab")
}
s_url = viper.GetString("url")
s_apiKey = viper.GetString("apikey")
fronius_ip = viper.GetString("fronius_ip")
pw_consumption = viper.GetFloat64("pw_consumption")
start_hr = viper.GetString("start_hr")
end_hr = viper.GetString("end_hr")
max_charge = viper.GetFloat64("max_charge")
pw_batt_reserve = viper.GetFloat64("pw_batt_reserve")
if len(viper.GetString("batt_reserve_start_hr")) == 0 {
batt_reserve_start_hr = viper.GetString("start_hr")
} else {
batt_reserve_start_hr = viper.GetString("batt_reserve_start_hr")
}
if s_defaults {
if _, exists := os.LookupEnv("DEFAULTS"); exists {
s_defaults = viper.GetBool("defaults")
}
if len(viper.GetString("batt_reserve_end_hr")) == 0 {
batt_reserve_end_hr = viper.GetString("end_hr")
} else {
batt_reserve_end_hr = viper.GetString("batt_reserve_end_hr")
}
crontab = viper.GetString("crontab")
s_defaults = viper.GetBool("defaults")

err := checkScheduleschedule(crontab, s_apiKey, s_url, fronius_ip, pw_consumption, max_charge, pw_batt_reserve, start_hr, end_hr)
if err != nil {
Expand All @@ -95,17 +75,17 @@ var scdCmd = &cobra.Command{
}

if crontab != "0 0 0 0 0" {
crontabSchedule(s_apiKey, s_url, fronius_ip, pw_consumption, max_charge, pw_batt_reserve, start_hr, end_hr, crontab, s_defaults)
crontabSchedule(s_apiKey, s_url, fronius_ip, pw_consumption, max_charge, pw_batt_reserve, start_hr, end_hr, crontab, s_defaults, batt_reserve_start_hr, batt_reserve_end_hr)

} else {
schedule(s_apiKey, s_url, fronius_ip, pw_consumption, max_charge, pw_batt_reserve, start_hr, end_hr)
schedule(s_apiKey, s_url, fronius_ip, pw_consumption, max_charge, pw_batt_reserve, start_hr, end_hr, batt_reserve_start_hr, batt_reserve_end_hr)

}
},
}

func init() {
scdCmd.Flags().StringVarP(&s_url, "url", "u", "", "Set the URL. For multiple URLs, use a comma (,) to separate them")
scdCmd.Flags().StringVarP(&s_url, "url", "u", "", "Set the Forecast URL. For multiple URLs, use a comma (,) to separate them")
scdCmd.Flags().StringVarP(&s_apiKey, "apikey", "k", "", "APIKEY")
scdCmd.Flags().StringVarP(&fronius_ip, "fronius_ip", "H", "", "FRONIUS_IP")
scdCmd.Flags().StringVarP(&start_hr, "start_hr", "s", const_sh, "START_HR")
Expand All @@ -114,6 +94,8 @@ func init() {
scdCmd.Flags().Float64VarP(&pw_consumption, "pw_consumption", "c", const_pc, "PW_CONSUMPTION")
scdCmd.Flags().Float64VarP(&max_charge, "max_charge", "m", const_mc, "MAX_CHARGE")
scdCmd.Flags().Float64VarP(&pw_batt_reserve, "pw_batt_reserve", "r", const_pbr, "PW_BATT_RESERVE")
scdCmd.Flags().StringVarP(&batt_reserve_start_hr, "batt_reserve_start_hr", "S", const_br_sh, "BATT_RESERVE_START_HR (default START_HR)")
scdCmd.Flags().StringVarP(&batt_reserve_end_hr, "batt_reserve_end_hr", "E", const_br_eh, "BATT_RESERVE_END_HR (default END_HR)")
scdCmd.Flags().BoolVarP(&s_defaults, "defaults", "d", true, "DEFAULTS")

viper.BindPFlag("url", scdCmd.Flags().Lookup("url"))
Expand All @@ -125,6 +107,8 @@ func init() {
viper.BindPFlag("crontab", scdCmd.Flags().Lookup("crontab"))
viper.BindPFlag("max_charge", scdCmd.Flags().Lookup("max_charge"))
viper.BindPFlag("pw_batt_reserve", scdCmd.Flags().Lookup("pw_batt_reserve"))
viper.BindPFlag("batt_reserve_start_hr", scdCmd.Flags().Lookup("batt_reserve_start_hr"))
viper.BindPFlag("batt_reserve_end_hr", scdCmd.Flags().Lookup("batt_reserve_end_hr"))
viper.BindPFlag("defaults", scdCmd.Flags().Lookup("defaults"))

rootCmd.AddCommand(scdCmd)
Expand All @@ -151,6 +135,27 @@ func isStartBeforeEnd(start, end string) bool {
return startTime.Before(endTime)
}

func isStartAfterEnd(start, end string) bool {
// Define a layout for parsing time strings
layout := "15:04"

// Parse the time strings
startTime, err := time.Parse(layout, start)
if err != nil {
u.Log.Error("Something goes wrong parsing start time")
panic(err)
}

endTime, err := time.Parse(layout, end)
if err != nil {
u.Log.Error("Something goes wrong parsing end time")
panic(err)
}

// Compare the times
return startTime.After(endTime)
}

func CheckTimeRange(start_hr string, end_hr string) bool {
now := time.Now()

Expand Down Expand Up @@ -200,14 +205,23 @@ func checkScheduleschedule(crontab string, apiKey string, url string, fronius_ip
} else if pw_batt_reserve < 0 {
err := errors.New("pw_batt_reserve must to be float > 0")
return err
} else if !isStartBeforeEnd(batt_reserve_start_hr, batt_reserve_end_hr) {
err := errors.New("batt_reserve_start_hr: " + batt_reserve_start_hr + " is not before batt_reserve_end_hr: " + batt_reserve_end_hr)
return err
} else if isStartAfterEnd(start_hr, batt_reserve_start_hr) {
err := errors.New("start_hr: " + start_hr + " is not before or equal batt_reserve_start_hr: " + batt_reserve_start_hr)
return err
} else if isStartAfterEnd(batt_reserve_end_hr, end_hr) {
err := errors.New("batt_reserve_end_hr: " + batt_reserve_end_hr + " is not before or equal end_hr: " + end_hr)
return err
}

return nil
}

func schedule(apiKey string, url string, fronius_ip string, pw_consumption float64, max_charge float64, pw_batt_reserve float64, start_hr string, end_hr string) {
func schedule(apiKey string, url string, fronius_ip string, pw_consumption float64, max_charge float64, pw_batt_reserve float64, start_hr string, end_hr string, batt_reserve_start_hr string, batt_reserve_end_hr string) {
if !CheckTimeRange(start_hr, end_hr) {
u.Log.Info("not in time range: " + start_hr + " <= t <= " + end_hr)
u.Log.Info("The current time is outside the range defined by start_hr and end_hr.: " + start_hr + " <= t <= " + end_hr)
} else {
pwr := pw.New()
solarPowerProduction, err := pwr.Handler(apiKey, url)
Expand All @@ -225,23 +239,23 @@ func schedule(apiKey string, url string, fronius_ip string, pw_consumption float
u.Log.Infof("your Daily consumption is:%d Wh", int(pw_consumption))

scd := fronius.New()
_, err = scd.Handler(solarPowerProduction, capacity2charge, capacity_max, pw_consumption, max_charge, pw_batt_reserve, start_hr, end_hr, fronius_ip)
_, err = scd.Handler(solarPowerProduction, capacity2charge, capacity_max, pw_consumption, max_charge, pw_batt_reserve, start_hr, end_hr, fronius_ip, CheckTimeRange(batt_reserve_start_hr, batt_reserve_end_hr))
if err != nil {
u.Log.Error(err)
panic(err)
}
}
}

func crontabSchedule(apiKey string, url string, fronius_ip string, pw_consumption float64, max_charge float64, pw_batt_reserve float64, start_hr string, end_hr string, crontab string, defaults bool) {
func crontabSchedule(apiKey string, url string, fronius_ip string, pw_consumption float64, max_charge float64, pw_batt_reserve float64, start_hr string, end_hr string, crontab string, defaults bool, batt_reserve_start_hr string, batt_reserve_end_hr string) {
layout := "15:04"
endTime, _ := time.Parse(layout, end_hr)
endTime = endTime.Add(-5 * time.Minute)
end_crontab := strconv.Itoa(endTime.Minute()) + " " + strconv.Itoa(endTime.Hour()) + " * * *"

c := cron.New()
_, err := c.AddFunc(crontab, func() {
schedule(apiKey, url, fronius_ip, pw_consumption, max_charge, pw_batt_reserve, start_hr, end_hr)
schedule(apiKey, url, fronius_ip, pw_consumption, max_charge, pw_batt_reserve, start_hr, end_hr, batt_reserve_start_hr, batt_reserve_end_hr)
})
if err != nil {
u.Log.Error(err)
Expand Down
Loading
Loading