Expand hints. Add RadioStatistics struct. Add new Order, DataAge and DataAgeMs.

This commit is contained in:
Pieter Hollander 2025-10-04 01:03:49 +02:00
parent 163e3672ef
commit 851c6d01ec
Signed by: pieter
SSH key fingerprint: SHA256:HbX+9cBXsop9SuvL+mELd29sK+7DehFfdVweFVDtMSg
2 changed files with 49 additions and 21 deletions

61
main.go
View file

@ -72,20 +72,34 @@ type InverterINV struct {
YieldTotal VUD `json:"YieldTotal"`
}
// RadioStatistics contains radio communication statistics
type RadioStatistics struct {
TxRequest int `json:"tx_request"`
TxReRequest int `json:"tx_re_request"`
RxSuccess int `json:"rx_success"`
RxFailNothing int `json:"rx_fail_nothing"`
RxFailPartial int `json:"rx_fail_partial"`
RxFailCorrupt int `json:"rx_fail_corrupt"`
RSSI float64 `json:"rssi"`
}
// Inverter struct
type Inverter struct {
Serial string `json:"serial"`
Name string `json:"name"`
Order int `json:"order"`
DataAge int `json:"data_age"`
DataAgeMs int `json:"data_age_ms"`
Producing bool `json:"producing"`
LimitRelative float64 `json:"limit_relative"`
LimitAbsolute float64 `json:"limit_absolute"`
AC map[string]InverterAC `json:"AC"`
DC map[string]InverterDC `json:"DC"`
Events int `json:"events"`
PollEnabled bool `json:"poll_enabled"`
Reachable bool `json:"reachable"`
DataAge int `json:"data_age"`
Events int `json:"events"`
AC map[string]InverterAC `json:"AC"`
DC map[string]InverterDC `json:"DC"`
INV map[string]InverterINV `json:"INV"`
RadioStats RadioStatistics `json:"radio_stats"`
}
type Total struct {
@ -98,6 +112,7 @@ type Hints struct {
TimeSync bool `json:"time_sync"`
RadioProblem bool `json:"radio_problem"`
DefaultPassword bool `json:"default_password"`
PinMappingIssue bool `json:"pin_mapping_issue"`
}
type LiveData struct {
@ -165,7 +180,7 @@ var config Config
// LoadConfig attempts to read the configuration from options.json
// If it fails, it falls back to using environment variables
func loadConfig() Config {
func loadConfig() (Config, error) {
configFilePath := os.Getenv("CONFIG_FILE")
if configFilePath == "" {
configFilePath = "/data/options.json"
@ -176,20 +191,20 @@ func loadConfig() Config {
// Successfully read the file, parse the JSON
err = json.Unmarshal(data, &config)
if err != nil {
log.Fatalf("Error parsing config file: %v", err)
return Config{}, fmt.Errorf("error parsing config file: %w", err)
}
if config.DB == "" {
log.Fatal("db connection settings are not set")
return Config{}, fmt.Errorf("db connection settings are not set")
}
if config.OpenDTUAddress == "" {
log.Fatal("opendtu_address is not set")
return Config{}, fmt.Errorf("opendtu_address is not set")
}
if config.OpenDTUAuth {
if config.OpenDTUUser == "" {
log.Fatal("opendtu_username is not set, while opendtu_auth is set to enabled. Set opendtu_auth to false or set username")
return Config{}, fmt.Errorf("opendtu_username is not set, while opendtu_auth is set to enabled. Set opendtu_auth to false or set username")
}
if config.OpenDTUPassword == "" {
log.Fatal("opendtu_password is not set, while opendtu_auth is set to enabled. Set opendtu_auth to false or set password")
return Config{}, fmt.Errorf("opendtu_password is not set, while opendtu_auth is set to enabled. Set opendtu_auth to false or set password")
}
}
} else {
@ -197,29 +212,29 @@ func loadConfig() Config {
// Fallback to environment variables
config.DB = os.Getenv("DB_URL")
if config.DB == "" {
log.Fatal("DB_URL environment variable is not set.")
return Config{}, fmt.Errorf("DB_URL environment variable is not set")
}
config.OpenDTUAddress = os.Getenv("OPENDTU_ADDRESS")
if config.OpenDTUAddress == "" {
log.Fatal("OPENDTU_ADDRESS environment variable is not set.")
return Config{}, fmt.Errorf("OPENDTU_ADDRESS environment variable is not set")
}
openDTUAuthStr := os.Getenv("OPENDTU_AUTH")
if openDTUAuthStr != "" {
openDTUAuth, err := strconv.ParseBool(openDTUAuthStr)
if err != nil {
log.Fatalf("Error parsing OPENDTU_AUTH: %v", err)
return Config{}, fmt.Errorf("error parsing OPENDTU_AUTH: %w", err)
}
config.OpenDTUAuth = openDTUAuth
}
if config.OpenDTUAuth {
config.OpenDTUUser = os.Getenv("OPENDTU_USERNAME")
if config.OpenDTUUser == "" {
log.Fatal("OPENDTU_USERNAME environment variable is not set.")
return Config{}, fmt.Errorf("OPENDTU_USERNAME environment variable is not set")
}
config.OpenDTUPassword = os.Getenv("OPENDTU_PASSWORD")
if config.OpenDTUPassword == "" {
log.Fatal("OPENDTU_PASSWORD environment variable is not set.")
return Config{}, fmt.Errorf("OPENDTU_PASSWORD environment variable is not set")
}
}
@ -228,7 +243,7 @@ func loadConfig() Config {
if timescaleDBStr != "" {
timescaleDB, err := strconv.ParseBool(timescaleDBStr)
if err != nil {
log.Fatalf("Error parsing TIMESCALEDB_ENABLED: %v", err)
return Config{}, fmt.Errorf("error parsing TIMESCALEDB_ENABLED: %w", err)
}
config.TimescaleDB = timescaleDB
}
@ -240,7 +255,7 @@ func loadConfig() Config {
logger.Warn("invalid timezone")
}
return config
return config, nil
}
// Helper function to map environment variable to slog.Level
@ -273,7 +288,11 @@ func main() {
slog.SetDefault(logger)
// Load the configuration
config := loadConfig()
var err error
config, err = loadConfig()
if err != nil {
log.Fatal(err)
}
// Set the logLevel
logLevel := getLogLevel(slog.LevelInfo) // Default to info level
@ -468,9 +487,9 @@ func insertLiveData(db *sql.DB, inverter Inverter, total Total, hints Hints) {
}
// Insert data into hints table
_, err = db.Exec(`
INSERT INTO opendtu_hints (timestamp, time_sync, radio_problem, default_password)
VALUES ($1, $2, $3, $4);
`, timestamp, hints.TimeSync, hints.RadioProblem, hints.DefaultPassword)
INSERT INTO opendtu_hints (timestamp, time_sync, radio_problem, default_password, pin_mapping_issue)
VALUES ($1, $2, $3, $4, $5);
`, timestamp, hints.TimeSync, hints.RadioProblem, hints.DefaultPassword, hints.PinMappingIssue)
if err != nil {
logger.Error("Error inserting into log table", "error", err)
return