All checks were successful
CI / changes (push) Successful in 19s
CI / test (push) Successful in 24s
CI / build-ai-gateway (push) Successful in 1m6s
CI / build-ha-gateway (push) Successful in 1m3s
CI / build-discord-bot (push) Successful in 1m4s
CI / build-alexa-bridge (push) Successful in 1m15s
CI / build-tts-gateway (push) Successful in 1m5s
CI / build-tts-sidecar (push) Has been skipped
CI / build-tts-model (push) Has been skipped
- Implemented SetTemperature in ClimateService for setting an absolute target temperature. - Updated ClimateServiceClient and ClimateServiceServer interfaces to include SetTemperature. - Added corresponding handler and tests for SetTemperature in ClimateGRPC. - Modified ClimateApp to handle SetTemperature requests without clamping. - Updated climate.proto to define SetTemperatureRequest message. - Adjusted Dockerfiles to include alexa-bridge dependencies.
139 lines
3.4 KiB
Go
139 lines
3.4 KiB
Go
package haclient
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
|
|
hav1 "gitea.nik4nao.com/nik/home-services/gen/ha/v1"
|
|
)
|
|
|
|
// optUint32 returns nil when key is absent. A present-but-unparsable value is
|
|
// treated as absent rather than failing the whole action — brightness/
|
|
// color-temp/transition are optional TurnOn/TurnOff fields, so a bad value
|
|
// there shouldn't block the rest of the request.
|
|
func optUint32(params map[string]any, key string) *uint32 {
|
|
v, ok := params[key]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
n, err := toUint32(v)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return &n
|
|
}
|
|
|
|
func reqUint32(params map[string]any, key string) (uint32, error) {
|
|
v, ok := params[key]
|
|
if !ok {
|
|
return 0, fmt.Errorf("missing required param %q", key)
|
|
}
|
|
return toUint32(v)
|
|
}
|
|
|
|
func reqFloat64(params map[string]any, key string) (float64, error) {
|
|
v, ok := params[key]
|
|
if !ok {
|
|
return 0, fmt.Errorf("missing required param %q", key)
|
|
}
|
|
return toFloat64(v)
|
|
}
|
|
|
|
func reqString(params map[string]any, key string) (string, error) {
|
|
v, ok := params[key]
|
|
if !ok {
|
|
return "", fmt.Errorf("missing required param %q", key)
|
|
}
|
|
s, ok := v.(string)
|
|
if !ok {
|
|
return "", fmt.Errorf("param %q = %v (%T), want string", key, v, v)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// optRGB mirrors optUint32's absent-or-unparsable-means-nil contract.
|
|
func optRGB(params map[string]any, key string) *hav1.RGBColor {
|
|
rgb, err := parseRGB(params, key)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return rgb
|
|
}
|
|
|
|
func reqRGB(params map[string]any, key string) (*hav1.RGBColor, error) {
|
|
rgb, err := parseRGB(params, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if rgb == nil {
|
|
return nil, fmt.Errorf("missing required param %q", key)
|
|
}
|
|
return rgb, nil
|
|
}
|
|
|
|
func parseRGB(params map[string]any, key string) (*hav1.RGBColor, error) {
|
|
v, ok := params[key]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
m, ok := v.(map[string]any)
|
|
if !ok {
|
|
return nil, fmt.Errorf("param %q = %v (%T), want map with r/g/b", key, v, v)
|
|
}
|
|
r, err := toUint32(m["r"])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("param %q.r: %w", key, err)
|
|
}
|
|
g, err := toUint32(m["g"])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("param %q.g: %w", key, err)
|
|
}
|
|
b, err := toUint32(m["b"])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("param %q.b: %w", key, err)
|
|
}
|
|
return &hav1.RGBColor{R: r, G: g, B: b}, nil
|
|
}
|
|
|
|
// toUint32/toFloat64 primarily parse strings, since params values arriving
|
|
// from Alexa slots (via internal/directive) are always strings — even
|
|
// AMAZON.NUMBER slots decode to their literal spoken-number string, e.g.
|
|
// "80". The float64/int fallback exists only so tests (or a hypothetical
|
|
// future non-Alexa caller) can pass typed values directly; it isn't
|
|
// exercised on the real Alexa -> directive -> haclient path.
|
|
func toUint32(v any) (uint32, error) {
|
|
switch t := v.(type) {
|
|
case string:
|
|
n, err := strconv.ParseUint(t, 10, 32)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("parse uint32 %q: %w", t, err)
|
|
}
|
|
return uint32(n), nil
|
|
case float64:
|
|
return uint32(t), nil
|
|
case int:
|
|
return uint32(t), nil
|
|
case uint32:
|
|
return t, nil
|
|
default:
|
|
return 0, fmt.Errorf("value %v (%T) is not a uint32-compatible type", v, v)
|
|
}
|
|
}
|
|
|
|
func toFloat64(v any) (float64, error) {
|
|
switch t := v.(type) {
|
|
case string:
|
|
n, err := strconv.ParseFloat(t, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("parse float64 %q: %w", t, err)
|
|
}
|
|
return n, nil
|
|
case float64:
|
|
return t, nil
|
|
case int:
|
|
return float64(t), nil
|
|
default:
|
|
return 0, fmt.Errorf("value %v (%T) is not a float64-compatible type", v, v)
|
|
}
|
|
}
|