- Implemented TurnOn, TurnOff, and Toggle methods in the gRPC client for switch control. - Added corresponding methods in the CommandApp to handle user commands for turning switches on, off, and toggling. - Created unit tests for the new switch control methods in CommandApp and SwitchApp. - Updated the HAGateway interface to include switch control methods. - Enhanced the SwitchGRPC service to handle switch control requests and return appropriate responses. - Added integration tests for the switch service to ensure correct behavior and error handling.
113 lines
3.1 KiB
Go
113 lines
3.1 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"sync"
|
|
|
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/domain"
|
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/core/ports/driven"
|
|
)
|
|
|
|
type SwitchApp struct {
|
|
ha driven.HAClient
|
|
mu sync.RWMutex
|
|
cache []domain.Switch
|
|
}
|
|
|
|
// NewSwitchApp constructs the switch application service.
|
|
func NewSwitchApp(ha driven.HAClient) *SwitchApp {
|
|
return &SwitchApp{ha: ha}
|
|
}
|
|
|
|
// Refresh repopulates the switch cache from the full Home Assistant state list.
|
|
func (a *SwitchApp) Refresh(ctx context.Context) error {
|
|
all, err := a.ha.ListStates(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var switches []domain.Switch
|
|
for _, s := range all {
|
|
if !strings.HasPrefix(s.EntityID, "switch.") {
|
|
continue
|
|
}
|
|
switches = append(switches, haStateToSwitch(s))
|
|
}
|
|
|
|
a.mu.Lock()
|
|
a.cache = switches
|
|
a.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// ListSwitches returns cached switch discovery data, refreshing lazily on first use.
|
|
func (a *SwitchApp) ListSwitches(ctx context.Context) ([]domain.Switch, error) {
|
|
a.mu.RLock()
|
|
c := a.cache
|
|
a.mu.RUnlock()
|
|
if c == nil {
|
|
if err := a.Refresh(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
a.mu.RLock()
|
|
c = a.cache
|
|
a.mu.RUnlock()
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// TurnOn maps application parameters into a Home Assistant switch.turn_on call.
|
|
func (a *SwitchApp) TurnOn(ctx context.Context, id domain.EntityID) (*domain.EntityState, error) {
|
|
payload := map[string]any{"entity_id": string(id)}
|
|
return a.callService(ctx, "switch", "turn_on", payload)
|
|
}
|
|
|
|
// TurnOff maps application parameters into a Home Assistant switch.turn_off call.
|
|
func (a *SwitchApp) TurnOff(ctx context.Context, id domain.EntityID) (*domain.EntityState, error) {
|
|
payload := map[string]any{"entity_id": string(id)}
|
|
return a.callService(ctx, "switch", "turn_off", payload)
|
|
}
|
|
|
|
// Toggle maps directly to Home Assistant switch.toggle.
|
|
func (a *SwitchApp) Toggle(ctx context.Context, id domain.EntityID) (*domain.EntityState, error) {
|
|
payload := map[string]any{"entity_id": string(id)}
|
|
return a.callService(ctx, "switch", "toggle", payload)
|
|
}
|
|
|
|
// callService falls back to GetState because Home Assistant may succeed without
|
|
// returning a full entity state list for the service call response.
|
|
func (a *SwitchApp) callService(ctx context.Context, svcDomain, service string, payload map[string]any) (*domain.EntityState, error) {
|
|
states, err := a.ha.CallService(ctx, svcDomain, service, payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
entityID, _ := payload["entity_id"].(string)
|
|
for _, s := range states {
|
|
if s.EntityID == entityID {
|
|
return haStateToDomain(s), nil
|
|
}
|
|
}
|
|
// HA may return an empty list on success; fall back to GetState.
|
|
s, err := a.ha.GetState(ctx, entityID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return haStateToDomain(s), nil
|
|
}
|
|
|
|
// haStateToSwitch extracts the subset of attributes needed for switch discovery.
|
|
func haStateToSwitch(s *driven.HAState) domain.Switch {
|
|
sw := domain.Switch{
|
|
EntityID: domain.EntityID(s.EntityID),
|
|
State: s.State,
|
|
}
|
|
if v, ok := s.Attributes["friendly_name"].(string); ok {
|
|
sw.FriendlyName = v
|
|
}
|
|
if v, ok := s.Attributes["device_class"].(string); ok {
|
|
sw.DeviceClass = v
|
|
}
|
|
return sw
|
|
}
|