Add --test-viessmann flag for end-to-end API validation

Adds GetDHWTemperature() to the Viessmann client (GET on the DHW feature
endpoint) and a --test-viessmann CLI flag that runs three stages without
starting the EMS control loop:

  Stage 1: read current DHW setpoint (confirms auth + endpoint path)
  Stage 2: write same value back, read-back to confirm round-trip
  Stage 3: write +1°C, read-back to confirm, restore original

The +1°C delta is below the heat pump's 5°C hysteresis so the compressor
will not fire, but the change is visible in the Viessmann app for visual
confirmation before trusting the WW boost logic in production.

Usage: ./ems --config /etc/ems/ems-config.yaml --test-viessmann

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 10:59:57 +02:00
parent 590ba1e675
commit 8ba86f8fc8
2 changed files with 154 additions and 12 deletions

View File

@@ -137,6 +137,52 @@ func (c *Client) featureURL(feature string) string {
)
}
// dhwFeatureResponse is the minimal structure returned by GET on the DHW temperature feature.
type dhwFeatureResponse struct {
Data struct {
Properties struct {
Value struct {
Value float64 `json:"value"`
} `json:"value"`
} `json:"properties"`
} `json:"data"`
}
// GetDHWTemperature reads the current domestic hot water target temperature from the Viessmann API.
func (c *Client) GetDHWTemperature(ctx context.Context) (float64, error) {
c.mu.Lock()
defer c.mu.Unlock()
if err := c.ensureToken(ctx); err != nil {
return 0, fmt.Errorf("ensuring token: %w", err)
}
featureURL := c.featureURL("heating.dhw.temperature.main")
req, err := http.NewRequestWithContext(ctx, http.MethodGet, featureURL, nil)
if err != nil {
return 0, fmt.Errorf("creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.token.AccessToken)
resp, err := c.httpClient.Do(req)
if err != nil {
return 0, fmt.Errorf("API call: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return 0, fmt.Errorf("Viessmann API returned %d", resp.StatusCode)
}
var result dhwFeatureResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return 0, fmt.Errorf("decoding response: %w", err)
}
return result.Data.Properties.Value.Value, nil
}
// SetDHWTemperature sets the domestic hot water target temperature via the Viessmann API.
func (c *Client) SetDHWTemperature(ctx context.Context, tempC float64) error {
c.mu.Lock()