### Main Application Loop with Periodic Fetching in Go Source: https://context7.com/funkolab/beemenergy/llms.txt Orchestrates the main application lifecycle including configuration loading from /data/options.json, MQTT broker connection setup, token refresh management, and periodic data fetching. Uses a ticker to run data collection at configured intervals with immediate startup execution followed by scheduled iterations. ```go func main() { var config Config // Load configuration from /data/options.json configFile := "/data/options.json" if _, err := os.Stat(configFile); err == nil { file, err := os.Open(configFile) if err != nil { slog.Error("failed to open config file", "error", err) os.Exit(1) } if err := json.NewDecoder(file).Decode(&config); err != nil { slog.Error("Failed to decode config file", "error", err) } file.Close() } // Retrieve MQTT configuration from Supervisor err := getMQTTInfo(&config) if err != nil { slog.Error("error retrieving MQTT info", "error", err) } // Wait for startup delay if configured if config.StartDelay > 0 { time.Sleep(time.Duration(config.StartDelay) * time.Second) } // Connect to MQTT broker setupMQTTClient(config) // Create ticker for periodic updates ticker := time.NewTicker(time.Duration(config.RefreshInterval) * time.Minute) defer ticker.Stop() // Run immediately on startup fetchBeemData(&config) // Then run on schedule for range ticker.C { fetchBeemData(&config) } } ``` -------------------------------- ### Retrieve Home Assistant MQTT Configuration (Go) Source: https://context7.com/funkolab/beemenergy/llms.txt Fetches MQTT broker connection details from the Home Assistant Supervisor API. It requires the SUPERVISOR_TOKEN environment variable and makes a GET request to the Supervisor's MQTT service endpoint. Updates a Config struct with MQTT host, port, username, and password. ```go func getMQTTInfo(config *Config) error { supervisorToken := os.Getenv("SUPERVISOR_TOKEN") if supervisorToken == "" { return fmt.Errorf("SUPERVISOR_TOKEN not set") } client := &http.Client{} req, err := http.NewRequest("GET", "http://supervisor/services/mqtt", nil) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+supervisorToken) req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("unexpected response %d: %s", resp.StatusCode, string(body)) } var mqttResp MQTTResponse if err := json.NewDecoder(resp.Body).Decode(&mqttResp); err != nil { return err } if mqttResp.Result != "ok" { return fmt.Errorf("supervisor API returned result: %s", mqttResp.Result) } // Example response: // { // "result": "ok", // "data": { // "host": "core-mosquitto", // "port": 1883, // "username": "homeassistant", // "password": "mqtt_password", // "protocol": "3.1.1" // } // } config.MQTTHost = mqttResp.Data.Host config.MQTTPort = mqttResp.Data.Port config.MQTTUsername = mqttResp.Data.Username config.MQTTPassword = mqttResp.Data.Password return nil } ``` -------------------------------- ### Initialize MQTT Client (Go) Source: https://context7.com/funkolab/beemenergy/llms.txt Establishes a connection to an MQTT broker using provided configuration details. It sets up client options including broker URL, client ID, and authentication credentials. It also configures connection and lost handlers for logging and automatic reconnection. ```go func setupMQTTClient(config Config) { opts := mqtt.NewClientOptions() brokerURL := fmt.Sprintf("tcp://%s:%d", config.MQTTHost, config.MQTTPort) // Example: tcp://core-mosquitto:1883 opts.AddBroker(brokerURL) opts.SetClientID("beem-energy-client") if config.MQTTUsername != "" { opts.SetUsername(config.MQTTUsername) opts.SetPassword(config.MQTTPassword) } opts.SetOnConnectHandler(func(client mqtt.Client) { slog.Info("Connected to MQTT broker", "url", brokerURL) }) opts.SetConnectionLostHandler(func(client mqtt.Client, err error) { slog.Error("connection to MQTT broker lost", "error", err) // Note: Paho MQTT client auto-reconnects by default }) mqttClient = mqtt.NewClient(opts) if token := mqttClient.Connect(); token.Wait() && token.Error() != nil { slog.Error("failed to connect to MQTT broker", "error", token.Error()) // Application will retry on next data fetch cycle } } ``` -------------------------------- ### Publish Complete Box Data to MQTT in Go Source: https://context7.com/funkolab/beemenergy/llms.txt Publishes all sensors for a Beem device to MQTT including current power output, daily/monthly energy production, WiFi signal strength, and connectivity status. Creates 6 Home Assistant entities with device information and measurement metadata. Includes error handling for disconnected MQTT clients. ```go func publishBoxToMQTT(box BoxData, deviceId, deviceName string) { if !mqttClient.IsConnected() { slog.Error("MQTT client not connected. Cannot publish data.") return } deviceInfo := MQTTDeviceInfo{ Identifiers: []string{deviceId}, Name: deviceName, Manufacturer: "Beem Energy", Model: "Solar Panel", SwVersion: "1.0", } // Publish current power output publishSensor(box, deviceInfo, deviceId, "power", "Current Power", "W", box.WattHour, "power", "measurement", "mdi:solar-power", "") // Publish daily energy production publishSensor(box, deviceInfo, deviceId, "energy_daily", "Daily Energy", "Wh", box.TotalDay, "energy", "total_increasing", "mdi:solar-power", "") // Publish monthly energy production publishSensor(box, deviceInfo, deviceId, "energy_month", "Monthly Energy", "Wh", box.TotalMonth, "energy", "total_increasing", "mdi:solar-power", "") // Publish WiFi signal strength publishSensor(box, deviceInfo, deviceId, "signal_strength", "Signal Strength", "dBm", box.LastDbm, "signal_strength", "measurement", "mdi:wifi", "diagnostic") // Calculate and publish last contact duration lastAlive := calculateLastContactDuration(box.LastAlive) publishSensor(box, deviceInfo, deviceId, "last_alive", "Last Alive", "s", lastAlive, "duration", "measurement", "mdi:clock-outline", "diagnostic") // Calculate and publish last production duration lastProduction := calculateLastContactDuration(box.LastProduction) publishSensor(box, deviceInfo, deviceId, "last_production", "Last Production", "s", lastProduction, "duration", "measurement", "mdi:solar-panel", "diagnostic") } ``` -------------------------------- ### Home Assistant Addon Configuration Schema in YAML Source: https://context7.com/funkolab/beemenergy/llms.txt Defines the addon configuration structure for Home Assistant including email/password authentication, refresh intervals, debug mode, optional MQTT broker override settings, and multi-architecture support. Specifies schema validation types and available services/discovery options. ```yaml name: "Beem Energy" url: "https://github.com/funkolab/BeemEnergy" description: "Connect your Beem Energy Solar Panel to Home Assistant" version: "0.2.5" slug: "beem_energy" homeassistant_api: true image: "ghcr.io/funkolab/beemenergy" services: - "mqtt:want" discovery: - "mqtt" options: beem_email: "" beem_password: "" refresh_interval: 1 debug: false start_delayseconds: 1 schema: beem_email: email beem_password: password refresh_interval: int debug: bool override_mqtt_user: str? override_mqtt_password: password? override_mqtt_server: str? override_mqtt_port: port? start_delayseconds: int? arch: - amd64 - aarch64 - armv7 ``` -------------------------------- ### Publish MQTT Sensor Data and Auto-Discovery Config (Go) Source: https://context7.com/funkolab/beemenergy/llms.txt This Go function, publishSensor, is responsible for publishing Home Assistant MQTT auto-discovery configuration and sensor state. It takes various parameters to define the sensor, such as device information, sensor type, name, unit, value, and device-specific attributes. The function first constructs the unique ID and MQTT topic for discovery and state, then marshals a discovery configuration struct into JSON. Finally, it publishes the discovery configuration and the sensor's state value to the respective MQTT topics. Dependencies include the `json` and `fmt` packages, and an initialized `mqttClient` for publishing. ```go func publishSensor(box BoxData, deviceInfo MQTTDeviceInfo, deviceId, sensorType, name, unit string, value interface{}, deviceClass, stateClass, icon, entityCategory string) { uniqueId := fmt.Sprintf("%s_%s", deviceId, sensorType) // Example: beem_energy_bs123456_power discoveryTopic := fmt.Sprintf("%s/sensor/%s/%s/config", "homeassistant", deviceId, sensorType) // Example: homeassistant/sensor/beem_energy_bs123456/power/config stateTopic := fmt.Sprintf("%s/sensor/%s/%s/state", "homeassistant", deviceId, sensorType) // Example: homeassistant/sensor/beem_energy_bs123456/power/state config := MQTTDiscoveryConfig{ Name: name, UniqueId: uniqueId, Device: deviceInfo, StateTopic: stateTopic, UnitOfMeasurement: unit, DeviceClass: deviceClass, StateClass: stateClass, Icon: icon, EntityCategory: entityCategory, } // Example discovery config JSON: // { // "name": "Current Power", // "unique_id": "beem_energy_bs123456_power", // "device": { // "identifiers": ["beem_energy_bs123456"], // "name": "Beem Energy SOLO", // "manufacturer": "Beem Energy", // "model": "Solar Panel", // "sw_version": "1.0" // }, // "state_topic": "homeassistant/sensor/beem_energy_bs123456/power/state", // "unit_of_measurement": "W", // "device_class": "power", // "state_class": "measurement", // "icon": "mdi:solar-power" // } configJson, err := json.Marshal(config) if err != nil { slog.Error("failed to marshal discovery config", "error", err) return } // Publish discovery config (retained) if token := mqttClient.Publish(discoveryTopic, 0, true, configJson); token.Wait() && token.Error() != nil { slog.Error("failed to publish discovery config", "error", token.Error()) return } var stateValue string switch v := value.(type) { case int: stateValue = fmt.Sprintf("%d", v) case float64: stateValue = fmt.Sprintf("%.2f", v) case string: stateValue = v default: stateValue = fmt.Sprintf("%v", v) } // Example state: "285" (watts) // Publish state value (retained) if token := mqttClient.Publish(stateTopic, 0, true, stateValue); token.Wait() && token.Error() != nil { slog.Error("failed to publish state", "error", token.Error()) return } } ``` -------------------------------- ### Fetch Beem Box Summary Data (Go) Source: https://context7.com/funkolab/beemenergy/llms.txt Retrieves current and historical energy production data for Beem devices. It makes a POST request to the Beem API with month and year parameters, requiring an authorization token. Returns a SummaryResponse or an error. ```go func getBoxSummary(token string) (SummaryResponse, error) { now := time.Now() summaryReq := SummaryRequest{ Month: int(now.Month()), // Example: 11 (November) Year: now.Year(), // Example: 2025 } jsonData, err := json.Marshal(summaryReq) if err != nil { return nil, fmt.Errorf("failed to marshal summary request: %w", err) } req, err := http.NewRequest("POST", "https://api-x.beem.energy/beemapp/box/summary", bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("failed to execute request: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusCreated { return nil, fmt.Errorf("summary request failed with status code %d: %s", resp.StatusCode, string(body)) } var summaryResp SummaryResponse if err := json.Unmarshal(body, &summaryResp); err != nil { return nil, fmt.Errorf("failed to unmarshal response: %w", err) } // Example response: // [{ // "boxId": 12345, // "name": "SOLO", // "serialNumber": "BS123456", // "wattHour": 285, // "totalDay": 1450, // "totalMonth": 42500, // "lastAlive": "2025-11-20T14:30:00Z", // "lastProduction": "2025-11-20T14:28:00Z", // "lastDbm": -65, // "power": 285 // }] return summaryResp, nil } ``` -------------------------------- ### Authenticate with Beem Energy API using JWT token Source: https://context7.com/funkolab/beemenergy/llms.txt Authenticates with the Beem Energy cloud API using user email and password credentials. Sends a POST request to the login endpoint, handles the HTTP response, and extracts the JWT access token from the response for subsequent API requests. Returns an error if authentication fails or the response cannot be parsed. ```go func login(config *Config) error { credentials := Credentials{ Email: "user@example.com", Password: "your_password", } jsonData, err := json.Marshal(credentials) if err != nil { return fmt.Errorf("failed to marshal credentials: %w", err) } req, err := http.NewRequest("POST", "https://api-x.beem.energy/beemapp/user/login", bytes.NewBuffer(jsonData)) if err != nil { return fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { return fmt.Errorf("failed to execute request: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusCreated { return fmt.Errorf("login failed with status code %d: %s", resp.StatusCode, string(body)) } var loginResp LoginResponse if err := json.Unmarshal(body, &loginResp); err != nil { return fmt.Errorf("failed to unmarshal response: %w", err) } config.Token = loginResp.AccessToken return nil } ``` -------------------------------- ### Validate JWT token expiration in Go Source: https://context7.com/funkolab/beemenergy/llms.txt Validates whether a JWT access token has expired by decoding the Base64-encoded payload, extracting the expiration claim (exp), and comparing it against the current Unix timestamp. Returns true if the token is expired or invalid, false if the token is still valid. Handles malformed tokens and missing expiration claims gracefully. ```go func isTokenExpired(token string) bool { parts := strings.Split(token, ".") if len(parts) != 3 { return true } payload := parts[1] for len(payload)%4 != 0 { payload += "=" } decoded, err := base64.RawURLEncoding.DecodeString(payload) if err != nil { return true } var claims map[string]interface{} if err := json.Unmarshal(decoded, &claims); err != nil { return true } exp, exists := claims["exp"] if !exists { return true } expTime, ok := exp.(float64) if !ok { return true } isExpired := time.Now().Unix() > int64(expTime) return isExpired } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.