Nik Afiq 8f7024edfa
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
feat(climate): add SetTemperature method to ClimateService
- 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.
2026-07-25 12:08:24 +09:00

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)
}
}